feat(846-2): 기계 줄 시간당 작업량 화면 — 산출근거 「시간당 작업량」 줄 먼저 · 비목 ÷ 시간당 작업량 · 제어 구역 일당 / 시간당 식 고르기 · 변수 칸 · 기계 보기 작업량 식

- 산출근거: 일당 「시간당 작업량 = 시공량 560 ㎥/일 ÷ (1 대 × 8 시간) = 70 ㎥/시간」 · 시간당 식 = q · K · f · E · cm 값 줄 + 한 층 분수 Q 식 · 비목 줄 「59,020 원/시간 ÷ 70 ㎥/시간 = 843.1 원」(금액 그대로)
- 제어 구역: 「시간당 작업량」 묶음 맨 위 — 방식 고르기(식 없는 기계 = 잠김 + 까닭) · 시간당 = 작업량 틀 변수 칸(고침 · 원래 값) · 덮음 = 기계덮음.<기계>.작업량
- 기계 보기: 왼쪽 작업량 카드(기계 정본 변수) · 오른쪽 풀이 · 서버가 작업량 틀 연결을 실음(M02_MyMachine_Sheet._work)
- 시험: test_846_2_work_view 새로 · test_845_2 GC000088 비목 줄 기대값을 새 꼴로 고침(846-2 표시 변경) · 헬퍼 가짜 DOM 이 진짜 DOM 처럼 요소를 옮김(같은 요소 두 줄 재사용 흠을 잡게)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-16 00:00:35 +09:00
co-authored by Claude Opus 5.5
parent 3a8ba840f4
commit c8cd98dec1
16 changed files with 925 additions and 22 deletions
+26 -9
View File
@@ -6,6 +6,7 @@
* 기계 · 단가산출 줄 = 비목마다 「노무비 : 59,020 × … = 금액」 — 제목 줄(「굴착기 0.6㎥」) 누름 = 그 문서(기계 시간당 경비 등)를
* 같은 계산기로 셈해 같은 꼴로 펼침(손료 식 · 연료 · 운전사 · 노 · 재 · 경 · 계) · 비목 수는 글(링크 꼴 없음 · 요구 687-11) ·
* 공식 줄 단가산출 결과 = 수 누름(▸) · 접으면 한 줄 · 출력엔 펼친 것만
* 기계 줄 작업량(846-2) = 「시간당 작업량 = …」 줄 먼저 · 비목 줄 「÷ 70 ㎥/시간」(`line_stack_work`)
* 값 = 계산기 셈 답 그대로(`calcDoc` · 줄 `answer.줄` · 합 `노무비` …) — 여기서 다시 셈 안 함 · 조판 = KaTeX(`line_stack_tex.ts`)
* ========================================================================== */
@@ -50,9 +51,11 @@ import {
unitText,
shownUnitOf,
} from "../common_util/common_util_line_stack_unit";
import { workAt } from "../common_util/common_util_line_stack_work";
import { partName } from "../common_util/common_util_machine_symbol_name";
import { katexNode, symTex, tex, txt, valTex, type TexCtx } from "./line_stack_tex";
import { lt } from "./line_stack_text";
import { workQty, workRows } from "./line_stack_work";
type Cols = Partial<Record<"sum" | (typeof COSTS)[number], number>>;
interface Row {
@@ -318,18 +321,30 @@ function rows(
// 값 · 단위 한 덩이(좁은 폭에서 줄이 바뀌어도 단위가 떨어지지 않게)
const glue = (...xs: (string | HTMLElement)[]): HTMLElement =>
el("span", { className: "lstk-pack", children: xs.filter((x) => x !== "") });
// 846-2 작업량 — 「시간당 작업량」 줄 먼저 · 수량 = 「÷ 시간당 작업량」
const w = workAt(a, i);
const wq = (): HTMLElement[] | null => workQty(doc, qty, w, scope, alias); // 줄마다 새 요소
const byWork = wq() !== null;
const workHead = (): void => {
if (!w) return;
const got = workRows(w, { doc, vals: scope, links: h.cache.links, miss: () => undefined, look }); // prettier-ignore
if (h.cache.takeMissing()) h.miss();
for (const r of got) push(r);
};
const qtyNode = (cost?: string): (string | HTMLElement)[] =>
qty === null
? []
: [
glue(
" × ",
mu.wrap && qn
? katexNode(`\\left(${tex(qn, { side: "val", vals: scope, inline: true, cost, alias })}\\right)`) // prettier-ignore
: exprNode(qty, "val", scope, true, cost, { alias }),
mu.qty ? ` ${mu.qty}` : "",
),
];
: byWork
? [glue(" ", ...wq()!)]
: [
glue(
" × ",
mu.wrap && qn
? katexNode(`\\left(${tex(qn, { side: "val", vals: scope, inline: true, cost, alias })}\\right)`) // prettier-ignore
: exprNode(qty, "val", scope, true, cost, { alias }),
mu.qty ? ` ${mu.qty}` : "",
),
];
const priceUnit = mu.price ? ` ${mu.price}` : "";
if (r.까닭 && r.금액 === null) {
push({ h: [`${name} : — `, el("span", { className: "lstk-miss", text: r.까닭 })] });
@@ -341,6 +356,7 @@ function rows(
const cols: Cols = {};
const title = name || (s ? (subOf(s)?.이름 ?? s) : "");
push({ h: s && subOf(s) ? fold(s, title) : [title], cls: "lname" });
workHead();
for (const c of COSTS) {
const part = r.비목?.[c];
if (!isNum(part) || (!part && !bundle[c])) continue;
@@ -364,6 +380,7 @@ function rows(
: [glue(exprNode(line.단가, "val", scope, true, undefined, { alias }), priceUnit)];
const c = String(line.비목);
const bare = !!line.합밖 && !line.비목; // 합 밖 소계(810-2) = 글에만 금액 · 비목 칸 빈칸
workHead();
push({
h: [`${name} : `, ...price, ...qtyNode(), ` = ${one(amt)} ${AMOUNT_UNIT}`],
cols: bare ? {} : { sum: amt, [c]: amt },