feat(B09): 「여기서 세지 않는 줄」을 할 일과 갈라 냄 — 이중계상 자리

B08 이 준비공 여섯 줄을 인계에 실으면서(`93546b85`) 세 갈래가 섞여 옴

- ⚠ `blocked_kind` 가 **없는데 `in_bill=false`** 면 **막힌 줄이 아님** —
  「다른 표에서 이미 섬」(벌목·지장목제거)·「이 노선엔 없음」(사방 시설).
  그것을 「우리가 만들어야 하는 것」에 얹으면 사용자가 세우려 들고
  **그것이 곧 이중계상**(㉠~㉦ 규칙에 걸림)
- 화면 목록을 **셋으로** 가름 — 입력하면 풀리는 것(2) · 우리가 만들 것(10) ·
  **여기서 세지 않는 줄(2)**
- 검산 줄 비고에 B08 사유 문구를 그대로 실음
- 짝 시험 — 같은 `in_bill=false` 라도 사유가 다르면 갈래가 달라야 함

실측(5174, 2f940d8a): 세 무리가 각각 뜨고 검산줄 5개가 수량만 보임

검증: pytest 212 통과(신규 1), tsc 0건

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 08:29:15 +09:00
co-authored by Claude Opus 5
parent 878e5931bf
commit fb86195576
4 changed files with 42 additions and 4 deletions
@@ -49,7 +49,13 @@ SUPPLY_UNKNOWN = "unknown"
SUPPLY_OWNER = "owner_supplied"
#: 막힘 갈래를 사람 말로. **할 일이 다르므로 화면에서 갈라 보인다.**
#: ⚠ `blocked_kind` 가 **`None` 인데 `in_bill=False`** 면 **막힌 줄이 아니다** —
#: 「다른 표에서 이미 섬」(벌목·지장목제거)이거나 「이 노선엔 없음」(사방 시설)이다.
#: 그것을 「우리가 만들어야 하는 것」에 얹으면 **결국 이중계상으로 간다**(㉠~㉦ 규칙).
_NOT_OUR_ROW = "not_our_row"
_BLOCKED_LABELS = {
_NOT_OUR_ROW: "여기서 세지 않는 줄",
"input_missing": "입력이 필요합니다",
"unit_data_missing": "원단위가 없습니다(우리가 만들 것)",
"formula_missing": "전개식이 없습니다(우리가 만들 것)",
@@ -343,7 +349,20 @@ def build_bill(
# ⚠ 코드 유무보다 **먼저** 가른다. 보정량계는 공종코드가 없어서가 아니라
# **검산용 줄이라서** 금액이 없는 것이다 — `missing` 으로 새면 「단가를 구해야 할
# 줄」로 잘못 읽힌다.
result.excluded.append(_excluded_row(item))
row = _excluded_row(item)
result.excluded.append(row)
if item.blocked_reason:
# ⚠ 「다른 표에서 이미 섬」과 「입력이 필요함」을 **갈라** 싣는다.
kind = item.blocked_kind or _NOT_OUR_ROW
result.missing.append(
{
"name": item.display_name,
"unit": item.unit,
"quantity": str(item.quantity),
"reason": f"{_BLOCKED_LABELS.get(kind, '막힘')}{item.blocked_reason}",
"blocked_kind": kind,
}
)
continue
if (item.composite_parts or item.composite_not_ready) and not item.work_item_code:
# 묶음 줄 — 품셈에 그 공종이 없어 **조각을 합쳐** 한 줄로 세운다
@@ -108,7 +108,14 @@ def _composite_row(
def _excluded_row(item: HandoffWorkItem) -> BillRow:
"""검산용 줄(`in_bill=false`). **수량만 보이고 단가·금액을 안 붙인다.**"""
"""`in_bill=false`. **수량만 보이고 단가·금액을 안 붙인다.**
세 갈래가 섞여 온다 — 갈라 적지 않으면 사용자가 할 일을 못 읽는다.
· **검산용**(보정량계·무대) — 합계 검산에만 쓰는 줄
· **막힌 줄**(`blocked_kind` 있음) — 입력이나 원단위를 기다림
· ⚠ **여기서 세지 않는 줄**(`blocked_kind` 없음) — 「다른 표에서 이미 섬」·
「이 노선엔 없음」. **이것을 할 일 목록에 얹으면 결국 이중계상이 된다.**
"""
return BillRow(
item_no="",
level=1,
@@ -118,7 +125,9 @@ def _excluded_row(item: HandoffWorkItem) -> BillRow:
unit=item.unit,
quantity=item.quantity,
in_bill=False,
note=item.in_bill_reason or "합계 검산용 줄 — 금액을 매기지 않습니다.",
note=item.blocked_reason
or item.in_bill_reason
or "합계 검산용 줄 — 금액을 매기지 않습니다.",
)
+7 -1
View File
@@ -913,13 +913,19 @@ export async function renderB09Estimation(root: HTMLElement): Promise<void> {
// (2026-09-08 메인 창 지적), 한 화면에 뜨는 줄 수를 늘리지 않는다.
// ⚠ **할 일이 다르므로 갈라 보인다** — 「사용자가 입력하면 풀리는 것」과
// 「우리가 만들어야 하는 것」. 한 목록에 섞으면 사용자가 무엇을 해야 할지 못 읽는다.
// ⚠ **세 갈래로 가른다.** 「여기서 세지 않는 줄」을 할 일 목록에 얹으면
// 사용자가 세우려 들고, 그것이 곧 이중계상이다(㉠~㉦ 규칙).
const notOurs = bill.summary.missing.filter((item) => item.blocked_kind === "not_our_row");
const needsInput = bill.summary.missing.filter(
(item) => item.blocked_kind === "input_missing",
);
const rest = bill.summary.missing.filter((item) => item.blocked_kind !== "input_missing");
const rest = bill.summary.missing.filter(
(item) => item.blocked_kind !== "input_missing" && item.blocked_kind !== "not_our_row",
);
for (const [labelKey, group] of [
["B09_Estimation_Boq_NeedsInput", needsInput],
["B09_Estimation_Boq_NeedsWork", rest],
["B09_Estimation_Boq_NotOurs", notOurs],
] as Array<[keyof typeof ui_locales, typeof bill.summary.missing]>) {
if (group.length === 0) continue;
const head = document.createElement("div");
+4
View File
@@ -725,6 +725,10 @@ export const ui_locales_b2 = {
"검산용 줄 — 수량만 보이고 금액을 매기지 않습니다",
"Check rows — quantity only, never priced",
],
B09_Estimation_Boq_NotOurs: [
"여기서 세지 않는 줄 (다른 표에 있거나 이 노선에 없음)",
"Counted elsewhere or not present on this route",
],
B09_Estimation_Boq_NeedsInput: [
"입력하면 풀리는 것 (설계 화면에서 값을 고르면 금액이 섭니다)",
"Waiting on input — pick the value on the design screen and the amount appears",