fix(M01): 로직 개선 시험 — 호표 묶음을 서버 비목 몫으로 나눔 · 「표 찾기 찾기」 겹침 · 모달 걸린 줄 맞힘

- 호표 묶음 소계가 텍스트 수식 소계와 같아짐(로직을 부르는 줄도 노무비·재료비·경비로 나뉘어 「그 밖」이 사라짐)
- 표 줄의 빈 칸은 조건에서 빼 서버 찾기()와 같게 걸린 줄을 맞힘
- 표 꼬리표를 「표」로 줄이고 걸린 줄 없음 안내글을 바로잡음

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-21 21:35:19 +09:00
co-authored by Claude Sonnet 5
parent bf7764fb99
commit 42243ba19b
3 changed files with 29 additions and 9 deletions
@@ -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;
}
@@ -34,7 +34,17 @@ function tableView(
table: TableRow,
values: Record<string, string>,
): 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", {
@@ -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<string, readonly [string, string]>;