diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts index 02d734f3..e6db241e 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts @@ -147,20 +147,27 @@ function infoBox(ctx: DetailContext): HTMLElement { ]); } +/** 계산된 줄은 서버가 준 비목별 몫으로 묶음에 나눠 넣음 — 로직을 부르는 줄도 노무비·재료비·경비로 갈림 */ +const spread = (e: Entry, line?: CalcLine): Entry[] => { + const parts = Object.entries(line?.비목 ?? {}).filter(([, v]) => v !== 0); + if (!parts.length) return [e]; + return parts.map(([k, v]) => ({ ...e, group: OF_COST[k] ?? "other", amount: v })); +}; + function entries(ctx: DetailContext): Entry[] { const middles: NamedFormula[] = ctx.row.중간 ?? []; const ho = ctx.row.호표 ?? []; - const out: Entry[] = ho.map((item, i) => { + const out: Entry[] = ho.flatMap((item, i) => { const line = ctx.lines?.[i]; const brief = ctx.prices[item.요소]; const price = line ? line.단가 : brief?.값; - return { + const entry: Entry = { group: groupOfHo(item), name: item.이름 ?? item.요소, spec: item.규격 ?? brief?.규격 ?? "", unit: item.단위 ?? "", qty: item.수량, - qtyTag: qtyKind(item.수량), + qtyTag: qtyKind(item.수량) === tx("Ho_QtyTable") ? tl("Tag_Table") : qtyKind(item.수량), price: price === undefined || price === null ? "" : formatNumber(price), amount: line ? line.금액 : null, extra: false, @@ -174,10 +181,11 @@ function entries(ctx: DetailContext): Entry[] { values: ctx.values, }), }; + return spread(entry, line); }); (ctx.row.덧줄 ?? []).forEach((extra, i) => { const line = ctx.lines?.[ho.length + i]; - out.push({ + const entry: Entry = { group: OF_COST[extra.비목 ?? ""] ?? "other", name: extra.이름, spec: "", @@ -195,7 +203,8 @@ function entries(ctx: DetailContext): Entry[] { middles, values: ctx.values, }), - }); + }; + out.push(...spread(entry, line)); }); return out; } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts index c1b7bf78..f6cd210a 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts @@ -34,7 +34,17 @@ function tableView( table: TableRow, values: Record, ): HTMLElement { - const hit = matchRow(table, find.find.conds, { values, middle: {} }); + // 표 줄의 빈 칸은 「무엇이든」 — 서버 찾기()와 같게 그 칸은 조건에서 뺌 + const hit = + (table.줄 ?? []).find((line) => + matchRow( + { ...table, 줄: [line] }, + find.find.conds.filter( + ([col]) => line[col] !== undefined && line[col] !== null && line[col] !== "", + ), + { values, middle: {} }, + ), + ) ?? null; const cols = [...Object.keys(table.조건 ?? {}), ...Object.keys(table.값칸 ?? {})]; const rows = (table.줄 ?? []).map((line) => el("tr", { diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts index 72b44b35..66fea422 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts @@ -27,7 +27,8 @@ const TEXT = { G_labor: ["인력", "Labor"], G_material: ["자재", "Material"], G_cost: ["경비", "Expense"], - G_other: ["그 밖", "Other"], + G_other: ["그 밖 (인력·자재·경비 어디에도 안 맞는 줄)", "Other (fits none of the three)"], + Tag_Table: ["표", "Table"], Subtotal: ["소계", "Subtotal"], Total: ["계", "Total"], Extra: ["덧줄", "Extra"], @@ -53,8 +54,8 @@ const TEXT = { Modal_NoData: ["표나 단가 자료가 없는 줄 — 수량 식만 있음", "No table or price data"], Modal_Hit: ["노란 줄 = 지금 넣은 값으로 걸린 줄", "Highlighted row matches the trial values"], Modal_NoHit: [ - "걸린 줄을 아직 못 맞힘 — 「시험 계산」 에 값을 넣고 다시 누르세요", - "No matching row yet — enter trial values and reopen", + "지금 값으로는 걸린 줄이 없음 — 값을 바꿔 보거나, 이 표를 안 쓰는 갈래일 수 있음", + "No row matches the current values — change them, or this table is on an unused branch", ], } as const satisfies Record;