Merge remote-tracking branch 'origin/dev' into main_laptop_1

This commit is contained in:
2026-09-14 08:33:32 +09:00
17 changed files with 149 additions and 21 deletions
@@ -54,6 +54,7 @@ from B08_Quantity.B08_Quantity_Engine_Handoff_Trench import (
from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import ( # noqa: F401
BLOCKED_FORMULA_MISSING,
BLOCKED_INPUT_MISSING,
BLOCKED_UNCONFIRMED,
BLOCKED_UNIT_DATA_MISSING,
METHOD_TO_GROUND,
NOTE_METHOD_MISSING,
@@ -138,6 +138,9 @@ REBAR_PREFIXES = ("이형철근", "원형철근", "철근")
BLOCKED_INPUT_MISSING = "input_missing" # 저장 제원 칸이 비어 있음 — 입력하면 풀림
BLOCKED_UNIT_DATA_MISSING = "unit_data_missing" # 원단위·표준 물량 자료가 없음
BLOCKED_FORMULA_MISSING = "formula_missing" # 수량 산출식 자체가 없음
#: 치수가 없어 **등록부 기본값으로 선** 구조물 줄(2026-09-14 브레인 판정) — 수량은 보이되 금액·합계엔
#: 안 듦. B09 가 내역 제자리에 빈 금액 + 빨간 테두리로 세우고 「미확정 N건 — 금액에 안 들어감」.
BLOCKED_UNCONFIRMED = "unconfirmed"
#: 사면 계열 — 토공집계에 함께 실리지만 출처가 사면적이다.
#: ⚠ `item` 칸이 **지반 갈래**인 공종 — 그 밖의 공종에서 `item` 은 **작업 갈래**다
@@ -13,6 +13,7 @@ from B08_Quantity.B08_Quantity_Engine_BasisUnit import normalize_unit, unit_for_
from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import (
BLOCKED_FORMULA_MISSING,
BLOCKED_INPUT_MISSING,
BLOCKED_UNCONFIRMED,
BLOCKED_UNIT_DATA_MISSING,
GROUND_SPLIT_GROUPS,
HAUL_SUMMARY_GROUPS,
@@ -34,6 +35,9 @@ from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import (
)
from B08_Quantity.B08_Quantity_Wording import type_label as wording_type_label
#: 기본값으로 선 구조물 줄 사유 머리 — B09 「미확정 N건 — 금액에 안 들어감」과 같은 말.
UNCONFIRMED_LABEL = "미확정 — 금액에 안 들어감"
def _spec_detail(structure: dict[str, Any]) -> str:
"""규격 표기 — 저장된 제원에서 만든다. 없는 값은 적지 않는다."""
@@ -450,6 +454,13 @@ def _structure_rows(
if notes
else "물량이 0 이라 내역에 안 세움 — 저장 제원에서 치수·면적을 넣으면 값이 섭니다"
)
# ⭐ 치수가 없어 기본값으로 선 구조물(2026-09-14 브레인 판정) — **줄은 서되 금액은 안 듦.**
# B09 가 내역 제자리에 빈 금액 + 빨간 테두리로 세우고 「미확정 N건 — 금액에 안 들어감」.
unconfirmed = str(structure.get("unconfirmed") or "")
if unconfirmed:
in_bill = False
zero_reason = f"{UNCONFIRMED_LABEL}{unconfirmed}"
blocked_kind, blocked_reason = BLOCKED_UNCONFIRMED, zero_reason
rows.append(
{
"work_item_code": code,
@@ -494,7 +505,7 @@ def _structure_rows(
"origin": ORIGIN_STRUCTURE,
}
)
if sheet_entry is not None:
if sheet_entry is not None and not unconfirmed:
rows[-1] = template_row(rows[-1], structure, sheet_entry)
return rows, unmatched
@@ -544,8 +555,10 @@ def _placing_rows(
# 2026-09-08 B09 가 「철근이 겹치나」를 물어 그 김에 드러난 자리다 —
# 철근은 안 겹치고(자재는 재료·묶음 조각은 품, 재료 0원) **타설이 겹쳤다.**
# ⚠ 묶음이 아직 미확보라 지금은 값이 안 걸렸을 뿐, 묶음이 서는 날 두 번이 된다.
if mapping.composite_for(str(structure.get("type_id") or "")):
continue
if mapping.composite_for(str(structure.get("type_id") or "")) or structure.get(
"unconfirmed"
):
continue # 기본값으로 선 구조물도 — 금액에 안 듦
# ⚠ 버림은 **따로 센다** — 실무 내역이 「레미콘타설(장비) **무근,버림**」으로 갈라
# 적는다(봉화 제50호표, 2026-09-09 데스크탑 보조 확인). 같은 공종·같은 단가라
# 금액은 안 움직이고 **이름만 맞추는 것**이다.
@@ -166,8 +166,10 @@ def rubble_base_rows(
total = 0.0
bases: list[str] = []
for structure in unit_quantity_table.get("structures") or []:
if mapping.composite_for(str(structure.get("type_id") or "")):
continue
if mapping.composite_for(str(structure.get("type_id") or "")) or structure.get(
"unconfirmed"
):
continue # 묶음 조각이 품음 · 기본값으로 선 구조물은 금액에 안 듦
sheet_entry = priced.get(str(structure.get("structure_id")))
if sheet_entry is not None and RUBBLE_GROUP in sheet_entry["covered"]:
continue
@@ -219,6 +221,8 @@ def structure_earthwork_rows(
# 사유도 안 났다). 관측 원단위로 가는 종류는 표에 터파기 줄이 없으면 이렇게 된다.
without_trench: list[str] = []
for structure in unit_quantity_table.get("structures") or []:
if structure.get("unconfirmed"):
continue # 기본값으로 선 구조물 — 제 줄에 사유가 섬, 토공 금액엔 안 듦
options = structure.get("options") or {}
height = float(structure.get("height_m") or options.get("height_m") or 0.0)
# 단면으로 판 깊이가 있으면 그것(옹벽은 기초분만 팜 · 비탈분 제외) — 없으면 직고 + 기초 깊이.
@@ -97,6 +97,8 @@ def haul_inputs(unit_quantity_table: dict[str, Any] | None) -> dict[str, Any]:
stone_by_ground: dict[str, float] = {}
stone_unknown = 0.0
for structure in unit_quantity_table.get("structures") or []:
if structure.get("unconfirmed"):
continue # 기본값으로 선 구조물 — 잔토·채집석이 사토 운반 금액으로 번지지 않게
amount = 0.0
stone = 0.0
for component in structure.get("components") or []:
@@ -286,6 +286,10 @@ def _supply_setting(supply: dict[str, Any], row: MaterialRow) -> Any:
return None
#: 건너뛴 까닭 칸 — 치수가 없어 기본값으로 선 구조물(구조물 수로 셈).
UNCONFIRMED_SKIP = "미확정(기본값으로 선 구조물)"
def _collect(
unit_quantity_table: dict[str, Any],
) -> tuple[dict[tuple[str, str, str], MaterialRow], dict[str, int]]:
@@ -293,6 +297,10 @@ def _collect(
rows: dict[tuple[str, str, str], MaterialRow] = {}
skipped: dict[str, int] = {}
for structure in unit_quantity_table.get("structures", []):
if structure.get("unconfirmed"):
# 기본값으로 선 구조물 — 자재에 안 듦(2026-09-14 브레인 판정 「없으면 없다고 보이기」).
skipped[UNCONFIRMED_SKIP] = skipped.get(UNCONFIRMED_SKIP, 0) + 1
continue
label = str(structure.get("name") or structure.get("type_id") or "")
for component in structure.get("components", []):
destination = str(component.get("destination") or "") or "(없음)"
+13 -6
View File
@@ -208,9 +208,14 @@ REVET_ROLES = (
("inlet", "유입부 기슭막이", "유입 칸 벽"),
("outlet", "유출부 기슭막이", "유출 칸 벽"),
)
#: ⚠ 「횡단도와 같은 값」이 아님 — B06 은 관 벽 높이를 **관경 기준 최소 높이**로 따로 그림
#: (`revetWallSpec`), 독립 기슭막이도 저장 높이가 없으면 근입 깊이로 그림. 형태·길이만 같은 기본값.
NOTE_DEFAULT_WALL = (
"⚠ 시설 지점에 {filled} 을 안 적어 등록부 기본값으로 섰음 — 횡단도 그림과 같은 값"
"⚠ 시설 지점에 {filled} 을 안 적어 등록부 기본값으로 섰음 — 미확정이라 금액에 안 들어감"
" · 횡단도 벽 높이는 관경 기준으로 따로 그려져 다를 수 있음"
)
#: 금액에서 뺄 까닭(2026-09-14 브레인 판정) — 줄·표·그림은 서되 내역 합에는 안 듦.
UNCONFIRMED_WALL = "벽 치수({filled})가 시설 지점에 없어 기본값으로 섰음 — 적으면 금액이 섬"
NOTE_SIDE_UNKNOWN = (
"⚠ 설치 측 「{side}」인데 두 칸(유입·유출) 값이 달라 어느 칸이 그쪽인지 서버가 못 가림"
"(횡단 지형이 정함) — 값을 안 세움 · 두 칸을 같게 적으면 섬"
@@ -231,7 +236,7 @@ def _num_or_zero(value: Any) -> float:
def _revet_values(
options: dict[str, Any], role: str, defaults: dict[str, Any], legacy: bool
) -> tuple[dict[str, Any], list[str]]:
"""벽 한쪽 제원과 등록부 기본값으로 채운 칸 — B06 횡단 그림(`_side_spec`)과 같은 채움."""
"""벽 한쪽 제원과 등록부 기본값으로 채운 칸 — B06 세트 스펙(`_side_spec`)과 같은 채움."""
values: dict[str, Any] = {}
filled: list[str] = []
for name, label in REVET_FIELDS:
@@ -250,7 +255,8 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""계곡 통과 시설(`pipe_points.json`) → 원단위 전개가 읽는 구조물 줄 (A1, 2026-09-14).
자체는 `build_rows` 연장으로 .
적힌 칸은 **등록부 기본값** 횡단 그림이 쓰는 값과 같고, 사실을 사유로 붙임.
적힌 칸은 **등록부 기본값** 사실을 사유로 붙이고 `unconfirmed` 금액 합에서
(2026-09-14 브레인 판정 줄은 서되 금액은 실제 값이 있을 때만).
집수정·기슭막이 터파기·되메우기는 전개 성분(`destination: earthwork`)이라 토공집계로만 .
"""
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
@@ -303,17 +309,17 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
notes = list(inlet_notes) if role == "inlet" else []
if filled:
notes.append(NOTE_DEFAULT_WALL.format(filled=" · ".join(filled)))
walls.append([role, own_label if legacy else pipe_label, values, notes, False])
walls.append([role, own_label if legacy else pipe_label, values, notes, False, filled])
side = str(options.get("side") or "") if legacy else ""
if side in ("", ""):
# 좌·우가 유입/유출 어느 칸인지는 **횡단 지형**이 정함 — 두 칸이 같을 때만 한 벽으로 셈.
same = walls[0][2] == walls[1][2]
walls = [[walls[0][0], f"{side}", walls[0][2], walls[0][3], not same]]
walls = [[walls[0][0], f"{side}", walls[0][2], walls[0][3], not same, walls[0][5]]]
if not same:
walls[0][3].append(NOTE_SIDE_UNKNOWN.format(side=side))
foundation = options.get("foundation" if legacy else "revet_foundation")
kept = {k: v for k, v in options.items() if not k.startswith(("inlet_", "outlet_"))}
for role, label, values, notes, withheld in walls:
for role, label, values, notes, withheld, filled in walls:
before, after = _num_or_zero(values["before_m"]), _num_or_zero(values["after_m"])
row = child_row(base, "revetment", label, f"{role}_revet")
row.update(
@@ -322,6 +328,7 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
options={**(kept if legacy else {}), **values, "foundation": foundation},
notes=notes,
withheld=withheld,
unconfirmed=UNCONFIRMED_WALL.format(filled=" · ".join(filled)) if filled else "",
)
rows.append(row)
return rows
@@ -26,6 +26,7 @@ from __future__ import annotations
from typing import Any
from B08_Quantity.B08_Quantity_Engine_Pipe import NOTE_DEFAULT_WALL
from B08_Quantity.B08_Quantity_Engine_StructureSheet import slope_of
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import (
_back_length,
@@ -283,9 +284,14 @@ def _masonry_figure(sheet: dict[str, Any]) -> list[dict[str, Any]]:
_LABEL_SIZE,
),
]
if default_note:
# 「이 값이 어디서 왔나」 — 표 사유 줄과 **글자까지 같게**(브레인 판정).
shapes.append(_text(default_note, (0.0, -0.80 - base_d), "left"))
# 「이 값이 어디서 왔나」 — 표 사유 줄과 **글자까지 같게**(브레인 판정). 벽 치수 자체가
# 기본값인 관 지점 기슭막이는 그 줄도 — 그림이 설계값처럼 보이면 더 위험함(2026-09-14).
wall_prefix = NOTE_DEFAULT_WALL.split("{")[0]
lines = [default_note] + [
str(n) for n in sheet.get("notes") or [] if str(n).startswith(wall_prefix)
]
for index, line in enumerate(line for line in lines if line):
shapes.append(_text(line, (0.0, -0.80 - base_d - index * 0.18), "left"))
return shapes
@@ -51,6 +51,7 @@ from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import ( # noqa: F401
Component,
StructureQuantity,
_num,
is_unconfirmed,
)
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Masonry import ( # noqa: F401 — 다시 내보냄
boulder_masonry,
@@ -528,6 +529,7 @@ def build_table(
if rubble is not None:
quantity.components.append(rubble)
_append_section_trench(quantity, item, observed, rubble_base_thickness_m)
quantity.unconfirmed = str(item.get("unconfirmed") or "")
quantities.append(quantity)
if use_templates:
# 늦게 부름 — 양식 모듈이 이 모듈을 부르므로 맨 위에서 부르면 맞물림.
@@ -544,7 +546,9 @@ def build_table(
violations = verify_no_mix_components(quantities)
totals: dict[str, dict[str, Any]] = {}
for item in quantities:
# 합은 **금액에 드는 구조물만** — 기본값으로 선 구조물(`unconfirmed`)은 줄에만 보임.
priced = [item for item in quantities if not item.unconfirmed]
for item in priced:
for component in item.components:
key = f"{component.name}|{component.spec}|{component.unit}"
entry = totals.setdefault(
@@ -581,6 +585,8 @@ def build_table(
"trench_depth_m": item.trench_depth_m,
# 양식 있음/없음 — 화면이 가림(비면 지금 전개).
"library_item": item.library_item,
# 기본값으로 선 까닭 — 차 있으면 금액·자재·토공·운반 합에서 빠짐(`is_unconfirmed`).
"unconfirmed": item.unconfirmed,
"notes": item.notes,
"components": [
{
@@ -619,7 +625,7 @@ def build_table(
COLLECTED_STONE_KEY: round(
sum(
component.amount
for item in quantities
for item in priced
for component in item.components
if component.name == "채집석"
),
@@ -108,6 +108,14 @@ class StructureQuantity:
trench_depth_m: float | None = None
#: 양식으로 성분을 세웠으면 그 양식 이름(PLAN 3장 ④-2). 비면 지금 전개 값.
library_item: str = ""
#: 치수가 정본에 없어 **등록부 기본값으로 선** 까닭(2026-09-14 브레인 판정) — 차 있으면 줄·표·그림은
#: 서되 **금액·자재·토공·운반 합에는 안 듦**(`is_unconfirmed`). 설계자가 치수를 적으면 비고 금액이 섬.
unconfirmed: str = ""
def is_unconfirmed(structure: dict[str, Any]) -> bool:
"""금액 합에서 뺄 구조물인가 — 합을 내는 자리마다 이 한 벌로 가림."""
return bool(structure.get("unconfirmed"))
def _num(value: Any, fallback: float = 0.0) -> float:
@@ -64,8 +64,12 @@ SUPPLY_OWNER = "owner_supplied"
#: 그것을 「우리가 만들어야 하는 것」에 얹으면 **결국 이중계상으로 간다**(㉠~㉦ 규칙).
_NOT_OUR_ROW = "not_our_row"
#: B08 `BLOCKED_UNCONFIRMED` 와 같은 글 — 치수 없이 기본값으로 선 줄.
BLOCKED_UNCONFIRMED = "unconfirmed"
_BLOCKED_LABELS = {
_NOT_OUR_ROW: "여기서 세지 않는 줄",
BLOCKED_UNCONFIRMED: "미확정 — 금액에 안 들어감",
"input_missing": "입력이 필요합니다",
"unit_data_missing": "원단위가 없습니다(우리가 만들 것)",
"formula_missing": "전개식이 없습니다(우리가 만들 것)",
@@ -123,6 +127,12 @@ class HandoffWorkItem:
def display_name(self) -> str:
return f"{self.name} {self.spec}".strip()
@property
def unconfirmed(self) -> bool:
"""B08 이 치수 없이 기본값으로 세운 줄(2026-09-14 브레인 판정) — 내역 제자리에
** 금액 + 빨간 테두리** 서고 합계에 · 머리에 미확정 N건 금액에 들어감."""
return self.blocked_kind == BLOCKED_UNCONFIRMED
@dataclass(frozen=True)
class HandoffMaterial:
@@ -262,6 +272,8 @@ class BillResult:
material_sheet: Any = None
#: 수동 단가로 선 자리 — 내역서 끝 「미확정 N건」(PLAN 확정 ⑦). 줄마다 `{name, count}`.
unconfirmed: list[dict[str, Any]] = field(default_factory=list)
#: 치수 없이 기본값으로 선 구조물 줄 — 제자리에 빈 금액으로 서고 **합계에 안 듦**(`{name, reason}`).
unpriced: list[dict[str, Any]] = field(default_factory=list)
@property
def direct_material_krw(self) -> Decimal:
@@ -435,7 +447,8 @@ def build_bill(
composites: list[HandoffWorkItem] = []
templated: list[HandoffWorkItem] = []
for item in work_items:
if not item.in_bill:
if not item.in_bill and not (item.unconfirmed and item.work_item_code in index):
# (미확정 줄은 제자리 나무로 감 — `_leaf_row` 가 금액 없이 세우고 `unpriced` 에 셈)
# ⚠ 코드 유무보다 **먼저** 가른다. 보정량계는 공종코드가 없어서가 아니라
# **검산용 줄이라서** 금액이 없는 것이다 — `missing` 으로 새면 「단가를 구해야 할
# 줄」로 잘못 읽힌다.
@@ -675,6 +688,8 @@ def bill_summary(result: BillResult) -> dict[str, Any]:
"missing": result.missing,
"unconfirmed": result.unconfirmed,
"unconfirmed_count": sum(int(entry["count"]) for entry in result.unconfirmed),
"unpriced": result.unpriced,
"unpriced_count": len(result.unpriced),
"body_total_krw": str(result.body_total_krw),
"direct_material_krw": str(result.direct_material_krw),
"direct_labor_krw": str(result.direct_labor_krw),
@@ -276,6 +276,10 @@ def _leaf_row(
# 검산용 줄 — 수량은 보이되 **단가를 안 붙인다**(PLAN 8-7 ㉡ 와 같은 성격).
row.quantity = item.quantity
row.add_note("", item.in_bill_reason or "합계 검산용 줄 — 금액을 매기지 않습니다.")
if item.unconfirmed:
# 치수 없이 기본값으로 선 줄 — 빨간 테두리 + 머리 「미확정 N건 — 금액에 안 들어감」.
row.unconfirmed = 1
result.unpriced.append({"name": item.display_name, "reason": item.blocked_reason})
result.excluded.append(row)
return row
@@ -129,6 +129,8 @@ export interface BillDto {
detail_rows: number;
missing: MissingDto[];
unconfirmed_count: number;
/** 치수 없이 기본값으로 선 구조물 줄 — 제자리에 빈 금액으로 서고 합계에 안 듦. */
unpriced_count?: number;
notes: string[];
material_sheet: MaterialSheetDto | null;
};
@@ -202,6 +202,15 @@ function drawBill(ctx: B09TabContext, bill: BillDto, reload: () => void): void {
);
if (bill.summary.unconfirmed_count > 0)
bar.append(unconfirmedBadge(bill.summary.unconfirmed_count));
// 치수 없이 기본값으로 선 줄 — 빨간 테두리 줄로 서고 금액·합계엔 안 듦(브레인 판정 2026-09-14).
if (bill.summary.unpriced_count)
bar.append(
el(
"span",
"b09s-badge",
`${L("B09_Sheet_Unconfirmed")} ${bill.summary.unpriced_count}${L("B09_Sheet_Count")}${L("B09_Sheet_Unpriced")}`,
),
);
const rateToggle = el("button", "b09s-undo", L("B09_Sheet_Rate"));
rateToggle.type = "button";
rateToggle.addEventListener("click", () => {
@@ -42,10 +42,41 @@ def test_관_하나에_기슭막이_둘이_기본값_사유와_함께_서고_관
work = {row["name"]: row for row in build_handoff(unit_quantity_table=table)["work_items"]}
assert work["배수관 · 유입부 기슭막이"]["work_item_code"] == "FP-13-04-05"
assert work["배수관 · 유출부 기슭막이"]["work_item_code"] == "FP-13-04-02"
assert (
work["배수관 · 유입부 기슭막이"]["in_bill"]
and work["배수관 · 유입부 기슭막이"]["unit"] == ""
assert work["배수관 · 유입부 기슭막이"]["unit"] == ""
def test_기본값으로_선_벽은_줄만_서고_금액_합에는_안_든다() -> None:
"""브레인 판정(2026-09-14) — 줄은 서야 채울 자리가 보이고, 금액은 실제 값이 있을 때만."""
point = {"chainage_m": 100.0, "options": {"pipe_diameter_mm": 1000}}
table = _table([point])
assert all(s["unconfirmed"] and s["components"] for s in table["structures"])
assert table["totals"] == [] # 원단위 합(자재·채집석 밑수)에도 안 듦
handoff = build_handoff(unit_quantity_table=table)
rows = handoff["work_items"]
walls = [r for r in rows if "기슭막이" in r["name"]]
assert len(walls) == 2 and all(
not r["in_bill"] and r["blocked_kind"] == "unconfirmed" for r in walls
)
assert walls[0]["quantity"] > 0 # 수량은 보임
# 터파기·되메우기·기초잡석·버림 타설이 금액 줄로 번지지 않음
assert not [r for r in rows if r["in_bill"] and r["quantity"] > 0]
from B09_Estimation.B09_Estimation_BillOfQuantities import bill_summary, build_bill
bill = build_bill(handoff)
placed = [r for r in bill.rows if not r.is_group and "기슭막이" in r.name]
assert len(placed) == 2 and all(r.unconfirmed == 1 and r.amount_krw is None for r in placed)
summary = bill_summary(bill)
assert summary["unpriced_count"] == 2 and summary["body_total_krw"] == "0"
# 치수를 다 적으면 미확정이 풀림
filled = {
f"{side}_revet_{key}": value
for side in ("inlet", "outlet")
for key, value in (("form", "돌쌓기(메)"), ("height_m", 1.5), ("length_m", 6))
}
confirmed = _table([{**point, "options": {**point["options"], **filled}}])
assert not any(s["unconfirmed"] for s in confirmed["structures"])
def test_유입구가_집수정이면_집수정_줄이_사유로_서고_유입_기슭막이는_없다() -> None:
@@ -131,10 +131,18 @@ def test_막힌_까닭이_세_갈래_중_하나일것() -> None:
from B08_Quantity.B08_Quantity_Engine_Handoff import (
BLOCKED_FORMULA_MISSING,
BLOCKED_INPUT_MISSING,
BLOCKED_UNCONFIRMED,
BLOCKED_UNIT_DATA_MISSING,
)
allowed = {None, BLOCKED_INPUT_MISSING, BLOCKED_UNIT_DATA_MISSING, BLOCKED_FORMULA_MISSING}
# ⭐ 넷째 갈래(2026-09-14 브레인 판정) — 치수 없이 기본값으로 선 줄: 수량은 보이되 금액 밖.
allowed = {
None,
BLOCKED_INPUT_MISSING,
BLOCKED_UNIT_DATA_MISSING,
BLOCKED_FORMULA_MISSING,
BLOCKED_UNCONFIRMED,
}
for row in _rows()["work_items"]:
assert row["blocked_kind"] in allowed
+1
View File
@@ -26,6 +26,7 @@ export const ui_locales_b3 = {
B09_Sheet_Won: ["원", " KRW"],
B09_Sheet_Count: ["건", ""],
B09_Sheet_Unconfirmed: ["미확정", "Unconfirmed"],
B09_Sheet_Unpriced: ["금액에 안 들어감", "not in the amount"],
B09_Sheet_Missing: ["금액을 못 세운 줄", "Rows without a price"],
B09_Sheet_Reload: ["다시 불러오기", "Reload"],
B09_Sheet_Level: ["보이는 레벨", "Show levels"],