fix(M02): 일위대가 목록 합계 · 단위 · 새 항목 두 단추 — 수량 없으면 기준 합(작은 글 「기준」) · 담은 로직 0 「—」 · 단위 = 일위대가 문서 단위 · 누른 단추가 진하게
39-8 B2 · B3 · B4 — 서버 목록 칸 labor 에 기준 · 단위 · laborSum · Col note · buildTwoWay choose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -22,12 +22,21 @@ export interface ItemRow extends TreeItem {
|
||||
도번?: string | null;
|
||||
면적번?: string | null;
|
||||
수정일?: Partial<Record<"structure" | "area" | "labor", string>>;
|
||||
labor?: { 번호: string | null; 로직수: number; 합계: number | null } | null;
|
||||
labor?: {
|
||||
번호: string | null;
|
||||
로직수: number;
|
||||
/** 수량 반영 합 · 기준(단가) 합 · 일위대가 문서 단위 */
|
||||
합계: number | null;
|
||||
기준?: number | null;
|
||||
단위?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface Col<R> {
|
||||
head: string;
|
||||
get: (row: R) => string;
|
||||
/** 값 뒤 작은 글(예 「기준」) — 없으면 없음 */
|
||||
note?: (row: R) => string;
|
||||
}
|
||||
|
||||
const TEXT = {
|
||||
@@ -51,6 +60,17 @@ const latest = (row: ItemRow): string =>
|
||||
.pop(),
|
||||
);
|
||||
|
||||
/** 일위대가 합계(39-8 B2) — 수량 반영 합이 있으면 그것 · 없으면 기준(단가) 합 + 작은 글 「기준」 ·
|
||||
* 담은 로직 0 · 계산값 없음 = 「—」 */
|
||||
export function laborSum(row: ItemRow): { text: string; note: string } {
|
||||
const l = row.labor;
|
||||
if (!l?.로직수) return { text: DASH, note: "" };
|
||||
if (l.합계) return { text: money(l.합계), note: "" };
|
||||
return typeof l.기준 === "number"
|
||||
? { text: money(l.기준), note: "기준" }
|
||||
: { text: DASH, note: "" };
|
||||
}
|
||||
|
||||
const NAME: Col<ItemRow> = { head: "이름", get: (r) => leafName(r) };
|
||||
const FOLDER: Col<ItemRow> = { head: "폴더", get: (r) => folderOf(r).join(" / ") };
|
||||
const UNIT: Col<ItemRow> = { head: "단위", get: (r) => r.단위 ?? "" };
|
||||
@@ -73,9 +93,9 @@ export const ITEM_COLS: Record<ListKind, Col<ItemRow>[]> = {
|
||||
{ head: "일위대가번", get: (r) => r.labor?.번호 || DASH },
|
||||
NAME,
|
||||
FOLDER,
|
||||
UNIT,
|
||||
{ head: "단위", get: (r) => r.labor?.단위 ?? "" }, // 일위대가 문서 단위(39-8 B3)
|
||||
{ head: "담은 로직 수", get: (r) => (r.labor ? String(r.labor.로직수) : DASH) },
|
||||
{ head: "합계", get: (r) => money(r.labor?.합계) },
|
||||
{ head: "합계", get: (r) => laborSum(r).text, note: (r) => laborSum(r).note },
|
||||
changed("labor"),
|
||||
],
|
||||
item: [
|
||||
@@ -184,7 +204,14 @@ export function buildItemList(): ItemListHandle {
|
||||
className: "m01-master__table-row",
|
||||
children: cols.map((c) => {
|
||||
const text = c.get(row);
|
||||
return el("td", { text, attrs: { title: text } });
|
||||
const note = c.note?.(row) ?? "";
|
||||
return el("td", {
|
||||
text,
|
||||
attrs: { title: note ? `${text} ${note}` : text },
|
||||
children: note
|
||||
? [el("small", { className: "m01-master__muted", text: ` ${note}` })]
|
||||
: [],
|
||||
});
|
||||
}),
|
||||
});
|
||||
// 고를 수 없는 줄 — 흐리게 · 누르면 까닭 한 줄(고르기 안 함)
|
||||
|
||||
@@ -185,10 +185,21 @@ export function buildTwoWay(opt: TwoWayOptions): HTMLElement {
|
||||
const copy = createButton({ label: TEXT.makeCopy, variant: "ghost" });
|
||||
const below = el("div", { className: "m02-itempick__below" });
|
||||
let busy = false;
|
||||
fresh.addEventListener("click", opt.onNew);
|
||||
/** 누른 단추 = 지금 화면 — 진한 단추가 고른 쪽 · 복사 생성 목록은 그쪽일 때만 보임 */
|
||||
const choose = (which: HTMLElement): void => {
|
||||
for (const b of [fresh, copy]) {
|
||||
b.classList.toggle("ui-btn--filled", b === which);
|
||||
b.classList.toggle("ui-btn--ghost", b !== which);
|
||||
}
|
||||
below.hidden = which !== copy;
|
||||
};
|
||||
fresh.addEventListener("click", () => {
|
||||
choose(fresh);
|
||||
opt.onNew();
|
||||
});
|
||||
copy.addEventListener("click", () => {
|
||||
choose(copy);
|
||||
if (below.firstChild) return;
|
||||
copy.classList.add("is-active");
|
||||
const picker = buildPicker({
|
||||
...opt.picker,
|
||||
onPick: (picked) =>
|
||||
|
||||
@@ -86,9 +86,19 @@ def items(folder: Path) -> list[dict[str, Any]]:
|
||||
return out
|
||||
|
||||
|
||||
def _number(sums: Any) -> float | None:
|
||||
"""`계산값` 합 칸의 `계` — 수 아닌 값(옛 꼴) · 없음 = null(「—」)."""
|
||||
total = sums.get("계") if isinstance(sums, dict) else None
|
||||
try:
|
||||
return None if total is None or isinstance(total, bool) else float(total)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _list_extras(folder: Path, col: str) -> dict[str, Any]:
|
||||
"""목록 칸(39-5) — `수정일` = 도면 · 면적산출 · 일위대가 파일 시각 · `labor` = 일위대가 문서의
|
||||
번호 · 담은 로직 수 · 합계(`계산값.반영합.계` · 없으면 null) — 파일이 없으면 칸 없음."""
|
||||
번호 · 담은 로직 수 · 합계(`계산값.반영합.계`) · 기준(`계산값.기준합.계`) · 단위 —
|
||||
파일이 없으면 칸 없음."""
|
||||
stamps: dict[str, str] = {}
|
||||
for kind in ("structure", "area", "labor"):
|
||||
path = folder / kind / f"{col}.json"
|
||||
@@ -103,15 +113,13 @@ def _list_extras(folder: Path, col: str) -> dict[str, Any]:
|
||||
doc = json.loads(path.read_bytes())
|
||||
except ValueError:
|
||||
doc = {}
|
||||
total = ((doc.get("계산값") or {}).get("반영합") or {}).get("계")
|
||||
try:
|
||||
total = None if total is None or isinstance(total, bool) else float(total)
|
||||
except (TypeError, ValueError):
|
||||
total = None # 수 아닌 값(옛 꼴)은 「—」
|
||||
calc = doc.get("계산값") or {}
|
||||
labor = {
|
||||
"번호": doc.get("일위대가번"),
|
||||
"로직수": len(doc.get("담은로직") or []),
|
||||
"합계": total,
|
||||
"합계": _number(calc.get("반영합")),
|
||||
"기준": _number(calc.get("기준합")), # 수량 없는 일위대가 = 단가 합(39-8 B2)
|
||||
"단위": doc.get("단위"), # 일위대가 문서 단위(구조물 열 단위 아님 · B3)
|
||||
}
|
||||
return {"수정일": stamps, "labor": labor}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const row = (열, 경로, extra = {}) => ({
|
||||
});
|
||||
const rows = [
|
||||
row("c1", ["기슭막이", "메쌓기", "1.2m"], {
|
||||
labor: { 번호: "SU-000001", 로직수: 3, 합계: 1234567.4 },
|
||||
labor: { 번호: "SU-000001", 로직수: 3, 합계: 1234567.4, 기준: 99, 단위: "개소" },
|
||||
수정일: { structure: "2026-10-01T09:00:00", labor: "2026-10-04T11:00:00" },
|
||||
}),
|
||||
row("c2", ["기슭막이", "찰쌓기"], { labor: { 번호: "SU-000002", 로직수: 0, 합계: null } }),
|
||||
@@ -62,6 +62,13 @@ process.stdout.write(
|
||||
pageOver: m.pageOf(many, m.ITEM_COLS.structure, "", 9).page,
|
||||
pageEmpty: m.pageOf([], m.ITEM_COLS.structure, "", 2).page,
|
||||
// 그 부분 문서 없는 줄(39-8 H3) — c1 = 일위대가 있음 · 면적 수정일 없음 · c3 = 수정일 · 일위대가 없음
|
||||
sums: [
|
||||
rows[0], // 반영 합 있음
|
||||
{ labor: { 로직수: 14, 합계: 0, 기준: 167746.6 } }, // 수량 없음 = 기준
|
||||
rows[1], // 담은 로직 0
|
||||
{ labor: { 로직수: 2, 합계: null, 기준: null } }, // 계산값 없음
|
||||
rows[2], // 일위대가 없음
|
||||
].map((r) => m.laborSum(r)),
|
||||
missing: ["structure", "area", "labor", "item"].map((k) =>
|
||||
rows.map((r) => m.missingPart(k, r)),
|
||||
),
|
||||
|
||||
@@ -54,11 +54,23 @@ def test_칸_값_빈_번호_합계_날짜(ts):
|
||||
s, labor = ts["cells"]["structure"], ts["cells"]["labor"]
|
||||
assert s[0] == ["SD-c1", "1.2m", "기슭막이 / 메쌓기", "m", "2026-10-01"]
|
||||
assert s[2][0] == "—" and s[2][2] == "" # 빈 도번 · 맨 위 항목(폴더 없음)
|
||||
assert labor[0][3:] == ["m", "3", "1,234,567", "2026-10-04"] # 합계 원 단위 반올림
|
||||
assert labor[0][3:] == ["개소", "3", "1,234,567", "2026-10-04"] # 일위대가 단위 · 원 단위
|
||||
assert labor[1][5] == "—" and labor[2][0] == labor[2][4] == "—" # 합계 없음 · 일위대가 없음
|
||||
assert ts["cells"]["item"][0][-1] == "2026-10-04" # 항목 통째 = 가장 늦게 고친 날
|
||||
|
||||
|
||||
def test_일위대가_합계_반영_기준_빈(ts):
|
||||
"""39-8 B2 — 반영 합이 있으면 그것 · 없으면 기준 합 + 「기준」 ·
|
||||
담은 로직 0 · 계산값 없음 = 「—」."""
|
||||
assert ts["sums"] == [
|
||||
{"text": "1,234,567", "note": ""},
|
||||
{"text": "167,747", "note": "기준"},
|
||||
{"text": "—", "note": ""},
|
||||
{"text": "—", "note": ""},
|
||||
{"text": "—", "note": ""},
|
||||
]
|
||||
|
||||
|
||||
def test_찾기_쪽(ts):
|
||||
assert ts["find"] == ["c2"] # 낱말 둘 AND(폴더 · 이름 칸)
|
||||
assert ts["findLabor"] == ["c1"] # 번호 칸도 찾음
|
||||
@@ -91,14 +103,27 @@ def test_서버_목록_칸_수정일_labor(world):
|
||||
doc = {
|
||||
"일위대가번": "SU-000001",
|
||||
"담은로직": [{"로직": "GF000219"}, {"로직": "GF000137"}],
|
||||
"계산값": {"반영합": {"계": 12345.6}},
|
||||
"단위": "개소",
|
||||
"계산값": {"반영합": {"계": 12345.6}, "기준합": {"계": 2469.12}},
|
||||
}
|
||||
(root / "labor" / f"{ditch['열']}.json").write_text(json.dumps(doc), encoding="utf-8")
|
||||
(root / "labor" / f"{wall['열']}.json").write_text("{깨짐", encoding="utf-8")
|
||||
rows = {r["열"]: r for r in t.client.get(t.url).json()["items"]}
|
||||
assert rows[ditch["열"]]["labor"] == {"번호": "SU-000001", "로직수": 2, "합계": 12345.6}
|
||||
assert rows[ditch["열"]]["labor"] == {
|
||||
"번호": "SU-000001",
|
||||
"로직수": 2,
|
||||
"합계": 12345.6,
|
||||
"기준": 2469.12,
|
||||
"단위": "개소",
|
||||
}
|
||||
assert "labor" in rows[ditch["열"]]["수정일"]
|
||||
assert rows[wall["열"]]["labor"] == {"번호": None, "로직수": 0, "합계": None}
|
||||
assert rows[wall["열"]]["labor"] == {
|
||||
"번호": None,
|
||||
"로직수": 0,
|
||||
"합계": None,
|
||||
"기준": None,
|
||||
"단위": None,
|
||||
}
|
||||
doc["계산값"] = {"반영합": {"계": "글"}}
|
||||
(root / "labor" / f"{ditch['열']}.json").write_text(json.dumps(doc), encoding="utf-8")
|
||||
again = {r["열"]: r for r in t.client.get(t.url).json()["items"]}
|
||||
|
||||
Reference in New Issue
Block a user