feat(753-2): 옵션으로 빠진 줄 숨김 — 상세 · 인력재료경비 · 시험 계산 · 텍스트 수식(M01 · M02 공용) 모두 안 그림
- 753-1 서버 줄 덧칸 `옵션꺼짐` 을 화면 넷이 읽어 그 줄을 안 그림 — 공용 상세 박스(`LogicLab_Ho` boxCard · 장비 · 하위 몫은 다 빠졌을 때만) · 텍스트 수식(`LogicLab_Formula`) · 시험 계산 표(`Logic_Calc` answerView) · 공용 계산서(`calc_sheet` · M01 인력재료경비 · M02 `sheetGroups`) - 옵션 칸 · 소계 · 금액 · 차례 · cid 그대로(옵션 칸은 빠진 줄의 쓰는 입력도 포함) — 옵션을 바꾸면 보이는 줄이 바뀜 - M02 표 줄 `CoverRow.off` · 계산서 줄 `옵션꺼짐` - 새 지킴 test_750_2 에 753-2 대조(표 줄 off → 계산서 줄 · 소계 그대로) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,8 @@ export interface SheetLine {
|
||||
장비?: boolean | null;
|
||||
기준단위?: string | null;
|
||||
환산근거?: string;
|
||||
/** (753-1) 옵션으로 빠진 줄 — 그리지 않음(소계 · 쓰는 입력 칸은 그대로) */
|
||||
옵션꺼짐?: boolean;
|
||||
/** 714-1b 장비 줄 펼침 — 부른 기계경비 로직의 시간당 줄(조종원 · 주연료 · 잡재료비 · 기계손료) */
|
||||
펼침?: SheetUnfold | null;
|
||||
}
|
||||
@@ -270,6 +272,10 @@ function headRows(
|
||||
const subtotal = (g: SheetGroup): HTMLElement =>
|
||||
createCalcFormulaRow(`${g.비목}${g.끝수 ? ` (${g.끝수})` : ""}`, null, out(g.소계, true));
|
||||
|
||||
/** 옵션으로 빠진 줄(753-1)은 안 그림 — 장비 · 하위 몫 줄은 몫이 다 빠졌을 때만 */
|
||||
const shown = (l: SheetLine | SheetMerged): boolean =>
|
||||
"몫" in l ? l.몫.some((x) => !x.줄.옵션꺼짐) : !l.옵션꺼짐;
|
||||
|
||||
/** 계산서 줄들 — 부르는 쪽 세로 칸(flex column)에 그대로 붙임 */
|
||||
export function calcSheetRows(o: SheetOptions): HTMLElement[] {
|
||||
const rows: HTMLElement[] = [];
|
||||
@@ -278,7 +284,8 @@ export function calcSheetRows(o: SheetOptions): HTMLElement[] {
|
||||
for (const b of bundleLines(o.묶음)) {
|
||||
const name = bundleName(b.묶음);
|
||||
rows.push(...headRows(name, b.쓰는입력, o, specs, b.기준단위, b.환산근거));
|
||||
for (const line of b.줄) rows.push(...("몫" in line ? mergedRows(line) : [lineRow(line)]));
|
||||
for (const line of b.줄.filter(shown))
|
||||
rows.push(...("몫" in line ? mergedRows(line) : [lineRow(line)]));
|
||||
rows.push(createCalcFormulaRow(name, null, out(b.소계, true, 1)));
|
||||
}
|
||||
rows.push(...o.묶음.map(subtotal)); // 맨 아래 비목 소계(서버 끝수 그대로) — 장비 노무는 여기서 인력 노무와 더해짐
|
||||
@@ -286,7 +293,7 @@ export function calcSheetRows(o: SheetOptions): HTMLElement[] {
|
||||
for (const g of o.묶음) {
|
||||
const uses = [...new Set(g.줄.flatMap((l) => l.쓰는입력 ?? []))];
|
||||
rows.push(...headRows(g.비목, uses, o, specs));
|
||||
rows.push(...g.줄.map((line) => lineRow(line)), subtotal(g));
|
||||
rows.push(...g.줄.filter(shown).map((line) => lineRow(line)), subtotal(g));
|
||||
}
|
||||
}
|
||||
rows.push(createCalcFormulaRow(o.sumLabel, null, out(o.계, true)));
|
||||
|
||||
@@ -158,7 +158,8 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
|
||||
onInput ? specs.find((x) => x.이름 === name) : undefined;
|
||||
// 737-2 비목마다 구분 상자 — 맨 위 옵션 칸 · 아래 두 칸 식 줄들 · 소계
|
||||
const boxes = text.groups.map((g) => {
|
||||
const lines = g.줄.map((line, i) => {
|
||||
const visible = g.줄.filter((l) => !l.옵션꺼짐); // 753-2 옵션으로 빠진 줄은 안 그림
|
||||
const lines = visible.map((line, i) => {
|
||||
const pair = String(line.짝 ?? `${g.비목}${i}`);
|
||||
const open = ctx.open?.(line);
|
||||
return el("div", {
|
||||
|
||||
@@ -615,9 +615,12 @@ function boxCard(
|
||||
const notes = [...new Set(lines.map((e) => e.textLine?.환산근거).filter(Boolean))];
|
||||
const { root, tbody } = tableShell(COLS, { density: "data" });
|
||||
root.classList.add("m01lab__ho");
|
||||
// 753-2 옵션으로 빠진 줄은 안 그림(옵션 칸 · 소계는 그대로 — 옵션을 바꾸면 보이는 줄이 바뀜)
|
||||
const off = (e: Entry): boolean => !!(e.line?.옵션꺼짐 || e.textLine?.옵션꺼짐);
|
||||
const rows = b.줄.filter((r) => !("몫" in r ? r.몫.every(off) : off(r)));
|
||||
tbody.append(
|
||||
...(b.줄.length
|
||||
? b.줄.flatMap((r) => ("몫" in r ? mergedRows(r, ctx) : [lineRow(r, ctx)]))
|
||||
...(rows.length
|
||||
? rows.flatMap((r) => ("몫" in r ? mergedRows(r, ctx) : [lineRow(r, ctx)]))
|
||||
: [emptyRow(COLS, tl("Empty_Group"))]),
|
||||
);
|
||||
const sub = tableRow(COLS, [
|
||||
|
||||
@@ -105,6 +105,8 @@ export interface CalcLine {
|
||||
까닭?: string;
|
||||
/** (M02 744-1) 둘째 기초 세부 줄 — `수량`은 M01 원문 · 이 칸은 1 기준단위 몫 비목(금액 · 비목은 머리 수량 곱한 상자 몫) */
|
||||
원문?: { 비목?: Record<string, number | null> };
|
||||
/** (753-1) 옵션으로 빠진 줄 — 화면은 안 그림 */
|
||||
옵션꺼짐?: boolean;
|
||||
}
|
||||
|
||||
/** % 덧줄 표기(149-4) — 율 있으면 수량 = 율 · 단위 % · 이름 뒤 규격 괄호 · 금액은 그대로(M01 · M02 한 벌) */
|
||||
@@ -160,6 +162,8 @@ export interface TextLine {
|
||||
환산근거?: string;
|
||||
/** (M02 750-1) SK 셈 답 줄 cid — 상세 줄과 이음 */
|
||||
cid?: string;
|
||||
/** (753-1) 옵션으로 빠진 줄(수량 0 이 만약 갈래 탓) — 화면은 그 줄을 안 그림(금액 · 차례 그대로) */
|
||||
옵션꺼짐?: boolean;
|
||||
}
|
||||
/** `POST /text` 답 — 서버가 만든 읽는 식 줄(화면은 그대로 찍음) */
|
||||
export type TextAnswer =
|
||||
|
||||
@@ -262,7 +262,8 @@ export function answerView(answer: CalcAnswer, draft: boolean, money: boolean):
|
||||
}
|
||||
const parts: HTMLElement[] = [...note];
|
||||
if (answer.lines) {
|
||||
const rows = answer.lines.map((line) =>
|
||||
const visible = answer.lines.filter((line) => !line.옵션꺼짐); // 753-2 옵션으로 빠진 줄은 안 그림
|
||||
const rows = visible.map((line) =>
|
||||
el("tr", {
|
||||
children: [
|
||||
el("td", {
|
||||
|
||||
@@ -90,6 +90,8 @@ export interface SkLine {
|
||||
원문?: { 수량: number | null; 금액: number | null; 비목?: Record<string, number | null> };
|
||||
/** 재료 줄이 잡은 품목(시험 계산 재료 칸 · 줄 모달) */
|
||||
품목?: CalcLine["품목"];
|
||||
/** (753-1) 옵션으로 빠진 줄 — 화면은 안 그림 */
|
||||
옵션꺼짐?: boolean;
|
||||
}
|
||||
|
||||
export interface SkMat {
|
||||
@@ -152,6 +154,8 @@ export interface CoverRow {
|
||||
sub: { k: number; cid: string; scale: number } | null;
|
||||
/** 화면배(728-2) — 문서 품 = 화면 품 × t · `was` = 문서 몫 */
|
||||
t: number;
|
||||
/** (753-2) 옵션으로 빠진 줄 — 계산서에서 안 그림 */
|
||||
off: boolean;
|
||||
}
|
||||
|
||||
/* ── 순수 셈 ── */
|
||||
@@ -238,7 +242,13 @@ export function rowsOf(ans: SkAnswer, bare: SkAnswer | null, doc: SkDoc): CoverR
|
||||
금액: number | null;
|
||||
},
|
||||
parts: Record<string, number>,
|
||||
extra: { 묶음?: string | null; 율?: number; apart?: boolean; 바탕값?: SkLine["바탕값"] },
|
||||
extra: {
|
||||
묶음?: string | null;
|
||||
율?: number;
|
||||
apart?: boolean;
|
||||
바탕값?: SkLine["바탕값"];
|
||||
옵션꺼짐?: boolean;
|
||||
},
|
||||
): CoverRow => {
|
||||
const hit = ADD_RE.exec(cid);
|
||||
const added = hit ? Number(hit[1]) : null;
|
||||
@@ -282,6 +292,7 @@ export function rowsOf(ans: SkAnswer, bare: SkAnswer | null, doc: SkDoc): CoverR
|
||||
apartPick: one?.종류 === "재료",
|
||||
sub: at ? { ...at, scale: Number(owner?.수량 ?? 1) } : null,
|
||||
t: pct || at ? 1 : t, // 세부 줄 품 = M01 원문 그대로(744-1) — 화면배 안 곱함
|
||||
off: !!extra.옵션꺼짐,
|
||||
};
|
||||
};
|
||||
for (const l of flatLines(ans)) rows.push(row(l.cid, l, partsOf(l), l));
|
||||
@@ -421,6 +432,7 @@ export function sheetGroups(rows: CoverRow[]): SheetGroup[] {
|
||||
금액: amount,
|
||||
묶음: r.묶음,
|
||||
장비: entries.length > 1 && (r.sub?.cid ?? r.cid).startsWith("logic:"),
|
||||
...(r.off ? { 옵션꺼짐: true } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,55 @@ def test_752_1_source_panel_only_on_sangeun_screen():
|
||||
assert "source" not in _ts("M02_MasterTemplete/M02_MasterTemplete_Labor_RowSk.ts")
|
||||
detail = _ts("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
assert "opt.source ? createSangeunSide()" in detail and "side?.set(" in detail
|
||||
|
||||
|
||||
def test_753_2_option_off_flag_reaches_sheet_and_views():
|
||||
"""753-2 옵션으로 빠진 줄 — SK 줄 `옵션꺼짐` → 표 줄 `off` → 계산서 줄 `옵션꺼짐`(공용 계산서 · 상세 ·
|
||||
텍스트 수식 · 시험 계산이 안 그림) · 소계 · 금액은 그대로."""
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
if shutil.which("node") is None:
|
||||
pytest.skip("node 가 없음")
|
||||
m02 = ROOT / "M02_MasterTemplete"
|
||||
strip = lambda p: re.sub(r"^import .*$", "", p.read_text("utf-8"), flags=re.M) # noqa: E731
|
||||
src = "\n".join(
|
||||
[
|
||||
strip(m02 / "logic" / "M02_Born_Tag.ts"),
|
||||
strip(m02 / "logic" / "M02_Sangeun_Undo.ts"),
|
||||
strip(m02 / "logic" / "M02_Sangeun_Cover_Model.ts"),
|
||||
"""
|
||||
const line = (cid, 이름, 수량, 금액, 꺼짐) => ({ cid, 이름, 단위: "개", 수량, 단가: 100, 금액,
|
||||
비목: { 재료비: 금액 }, 묶음: "재료", ...(꺼짐 ? { 옵션꺼짐: true } : {}) });
|
||||
const ans = { 기준: null, 짝잃음: [], 바탕판: "x", 정체: ["material:A#0", "material:B#0"],
|
||||
줄: [line("material:A#0", "폼타이 12.7", 0, 0, true), line("material:B#0", "합판", 1, 100, false)] };
|
||||
const rows = rowsOf(ans, null, {});
|
||||
const groups = sheetGroups(rows);
|
||||
console.log(JSON.stringify({ off: rows.map((r) => r.off),
|
||||
sheet: groups.map((g) => [g.비목, g.소계, g.줄.map((x) => [x.이름, x.옵션꺼짐 ?? false, x.금액])]) }));
|
||||
""",
|
||||
]
|
||||
)
|
||||
out = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", src],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert out.returncode == 0, out.stderr
|
||||
got = json.loads(out.stdout)
|
||||
assert got["off"] == [True, False]
|
||||
assert got["sheet"] == [["재료비", 100, [["폼타이 12.7", True, 0], ["합판", False, 100]]]]
|
||||
sheet = (ROOT / "A00_Common" / "calc_sheet.ts").read_text("utf-8")
|
||||
assert "b.줄.filter(shown)" in sheet and "g.줄.filter(shown)" in sheet
|
||||
ho = (ROOT / "M01_MasterData" / "M01_MasterData_UI_LogicLab_Ho.ts").read_text("utf-8")
|
||||
assert "옵션꺼짐" in ho
|
||||
assert "옵션꺼짐" in (
|
||||
ROOT / "M01_MasterData" / "M01_MasterData_UI_LogicLab_Formula.ts"
|
||||
).read_text("utf-8")
|
||||
assert "옵션꺼짐" in (ROOT / "M01_MasterData" / "M01_MasterData_UI_Logic_Calc.ts").read_text(
|
||||
"utf-8"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user