feat(717-2b): SK 더한 재료 줄 = 기본 계 안(재료 소계 · 계에 더함) · 줄 「별도계상」 고름만 계 밖
- 서버 M02_Sangeun_Doc — 더한줄 재료 칸 별도계상(true 만 남김) · patched 가 더한 재료 줄 자재표시를 직접계산(기본) | 별도계상(칸 켬)으로 둠 · 원문 로직 줄 별도계상은 그대로 - 화면 — SK 상세 고침 표 더한 재료 줄 로직 칸에 「별도계상」 칩(켬 · 끔) · 순수 함수 setApart · 행 apartPick - 지킴 시험 — test_715_sangeun_engine 더한 재료 계 안 · 별도계상 계 밖 · test_717_2b 칩 꼴 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createButton } from "@ui/ui_template_button";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { createInputField } from "@ui/ui_template_input";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
@@ -181,6 +182,8 @@ export interface SkCoverOptions {
|
||||
onPrice: (row: CoverRow, value: number | null) => void;
|
||||
onDrop: (row: CoverRow) => void;
|
||||
onRestore: (cid: string) => void;
|
||||
/** 더한 재료 줄 별도계상 켬 · 끔 */
|
||||
onApart: (row: CoverRow, on: boolean) => void;
|
||||
}
|
||||
|
||||
const ORDER = ["인력", "재료", "경비", "하위"];
|
||||
@@ -243,7 +246,18 @@ function rowCells(r: CoverRow, o: SkCoverOptions): (string | Node)[] {
|
||||
...(r.covered.qty || r.covered.price ? [createBadge(o.tag, "warning")] : []),
|
||||
...(r.added !== null ? [createBadge(L("M02_SangeunAddedTag"), "accent")] : []),
|
||||
...(r.pct ? [createBadge(L("M02_SangeunPct"), "neutral")] : []),
|
||||
...(r.apart ? [createBadge(L("M02_SangeunApart"), "neutral")] : []),
|
||||
...(can && r.apartPick
|
||||
? [
|
||||
createCheckbox(L("M02_SangeunApart"), {
|
||||
chip: true,
|
||||
size: 22,
|
||||
checked: r.apart,
|
||||
onChange: (on) => o.onApart(r, on),
|
||||
}).root,
|
||||
]
|
||||
: r.apart
|
||||
? [createBadge(L("M02_SangeunApart"), "neutral")]
|
||||
: []),
|
||||
...(r.revised ? [createBadge(L("M02_SangeunRevised"), "danger")] : []),
|
||||
];
|
||||
const drop = createButton({
|
||||
|
||||
@@ -33,6 +33,7 @@ import { openSangeunLinePicker } from "./M02_MasterTemplete_Sangeun_Pick";
|
||||
import {
|
||||
dropLine,
|
||||
restoreLine,
|
||||
setApart,
|
||||
rowsOf,
|
||||
setPrice,
|
||||
setQty,
|
||||
@@ -231,6 +232,7 @@ export function mountSangeunDetail(
|
||||
onPrice: (r, v) => (setPrice(doc, r, v), touch()),
|
||||
onDrop: (r) => (dropLine(doc, r), touch()),
|
||||
onRestore: (cid) => (restoreLine(doc, cid), touch()),
|
||||
onApart: (r, on) => (setApart(doc, r, on), touch()),
|
||||
});
|
||||
const row = base?.logic ?? standIn();
|
||||
const est = el("div", {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
입력값 {이름: 값}, 열린입력 [이름](설계 때 정함 · 그 SK 품 덮개 = 배율만), 켜쌓기율?,
|
||||
고름 {cid: MT키}, 자재표시 {cid: {세 칸}},
|
||||
덮개 {cid: {qty | 배율, labor, material, expense, 원값}},
|
||||
더한줄 [{종류: 인력|재료|기계, 요소, 이름, 단위, 수량, 비목?, 단가?}
|
||||
더한줄 [{종류: 인력|재료|기계, 요소, 이름, 단위, 수량, 비목?, 단가?,
|
||||
별도계상?(재료 · 없음 = 계 안)}
|
||||
| {종류: 로직, 로직, 입력값, 수량} | {참조 | 고정, 이름?, 단위?, 수량}],
|
||||
뺀줄 [cid], 바탕판}
|
||||
줄 정체 cid = `kind:키#n`(B09 702 덮개 꼴) — kind = labor · material · machine · logic · pct(덧줄) ·
|
||||
@@ -179,6 +180,8 @@ def _added(raw: Any, where: str, whole: Any) -> dict:
|
||||
head = {"종류": raw["종류"], "요소": key, "비목": part}
|
||||
if raw.get("단가") is not None:
|
||||
head["단가"] = plain(_num(raw["단가"], f"{where} 단가"))
|
||||
if raw["종류"] == "재료" and raw.get("별도계상") is True:
|
||||
head["별도계상"] = True # 더한 재료 기본 = 계 안 · 이 칸만 별도계상(`patched` 자재표시)
|
||||
name = name or str((got or {}).get("이름") or key)
|
||||
unit = unit or str((got or {}).get("단위") or "")
|
||||
else:
|
||||
@@ -360,6 +363,10 @@ def patched(
|
||||
new_items.append(item)
|
||||
cids.append(cid)
|
||||
for k, one in enumerate(doc.get("더한줄") or []):
|
||||
if one.get("종류") == "재료": # 더한 재료 = 기본 계 안(직접계산) · `별도계상` 칸만 밖
|
||||
out_marks[str(len(new_items))] = {
|
||||
"별도계상": "별도계상" if one.get("별도계상") else "직접계산"
|
||||
}
|
||||
new_items.append(_add_item(one))
|
||||
cids.append(f"add:#{k}")
|
||||
cids += [c for at, c in enumerate(ids) if at >= len(items) and c not in drop]
|
||||
|
||||
@@ -107,6 +107,8 @@ export interface CoverRow {
|
||||
/** 저장 때 적힌 원값이 지금 바탕 값과 다름(M01 개정) */
|
||||
revised: boolean;
|
||||
priceEdit: boolean;
|
||||
/** 더한 재료 줄 — 별도계상 고름(기본 계 안) */
|
||||
apartPick: boolean;
|
||||
}
|
||||
|
||||
/* ── 순수 셈 ── */
|
||||
@@ -183,6 +185,7 @@ export function rowsOf(ans: SkAnswer, bare: SkAnswer | null, doc: SkDoc): CoverR
|
||||
((old.qty != null && !near(old.qty, base.qty)) ||
|
||||
(old.단가 != null && !near(old.단가, base.단가))),
|
||||
priceEdit: !pct && (added === null || ["인력", "재료", "기계"].includes(String(one?.종류))),
|
||||
apartPick: one?.종류 === "재료",
|
||||
};
|
||||
};
|
||||
for (const l of ans.줄) rows.push(row(l.cid, l, partsOf(l), l));
|
||||
@@ -263,6 +266,14 @@ export function dropLine(doc: SkDoc, r: CoverRow): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 더한 재료 줄 별도계상 — 켬 = 계 밖(재료 목록만 · 서버 자재표시) · 끔 = 칸 지움(기본 계 안) */
|
||||
export function setApart(doc: SkDoc, r: CoverRow, on: boolean): void {
|
||||
const one = r.added === null ? undefined : doc.더한줄?.[r.added];
|
||||
if (!one || one.종류 !== "재료") return;
|
||||
if (on) one.별도계상 = true;
|
||||
else delete one.별도계상;
|
||||
}
|
||||
|
||||
/** 뺀 줄 되살림 */
|
||||
export function restoreLine(doc: SkDoc, cid: string): void {
|
||||
doc.뺀줄 = (doc.뺀줄 ?? []).filter((c) => c !== cid);
|
||||
|
||||
@@ -84,3 +84,17 @@ def test_added_ref_and_fixed_lines(world):
|
||||
assert got["기준"]["계"] == pytest.approx(unit * 2 + 5000, abs=1)
|
||||
expense = labor.run_line(_whole(), PCT, {})["기준"]["경비"]
|
||||
assert got["기준"]["경비"] == pytest.approx(5000 + 2 * float(expense), abs=1)
|
||||
|
||||
|
||||
def test_added_material_counts_unless_apart():
|
||||
"""사용자가 더한 재료 줄 = 기본 계 안(재료비 · 계에 더함) · 줄 「별도계상」 칸만 계 밖(재료 목록만)."""
|
||||
one = {"종류": "재료", "요소": "MT000010", "수량": 3, "단가": 1200}
|
||||
bare = _run(바탕={"로직": PCT})
|
||||
inside = _run(바탕={"로직": PCT}, 더한줄=[one])
|
||||
assert inside["기준"]["재료비"] == pytest.approx(bare["기준"]["재료비"] + 3600)
|
||||
assert inside["기준"]["계"] == pytest.approx(bare["기준"]["계"] + 3600, abs=1)
|
||||
assert any(x["cid"] == "add:#0" for x in inside["줄"])
|
||||
apart = _run(바탕={"로직": PCT}, 더한줄=[{**one, "별도계상": True}])
|
||||
assert apart["기준"]["계"] == pytest.approx(bare["기준"]["계"])
|
||||
assert not any(x["cid"] == "add:#0" for x in apart["줄"])
|
||||
assert any(m["금액"] == pytest.approx(3600) for m in apart["재료"])
|
||||
|
||||
@@ -74,6 +74,11 @@ out.split = doc.덮개["logic:G#0"];
|
||||
setQty(doc, by("add:#0"), 3, false);
|
||||
setPrice(doc, by("add:#0"), 350);
|
||||
out.added = doc.더한줄[0];
|
||||
out.pick = [by("add:#0").apartPick, by("labor:A#0").apartPick];
|
||||
setApart(doc, by("add:#0"), true);
|
||||
out.apartOn = doc.더한줄[0].별도계상 ?? null;
|
||||
setApart(doc, by("add:#0"), false);
|
||||
out.apartOff = doc.더한줄[0].별도계상 ?? null;
|
||||
const opened = { 덮개: {} };
|
||||
setQty(opened, by("labor:A#0"), 1.5, true);
|
||||
out.scale = opened.덮개["labor:A#0"];
|
||||
@@ -105,6 +110,9 @@ console.log(JSON.stringify(out));
|
||||
assert got["split"]["labor"] == 60 and got["split"]["material"] == 30
|
||||
assert got["split"]["expense"] == 10
|
||||
assert got["added"]["수량"] == 3 and got["added"]["단가"] == 350
|
||||
# 더한 재료 줄만 별도계상 고름 · 켬 = 칸 true · 끔 = 칸 지움(기본 계 안)
|
||||
assert got["pick"] == [True, False]
|
||||
assert got["apartOn"] is True and got["apartOff"] is None
|
||||
assert got["scale"]["배율"] == 1.5 and "qty" not in got["scale"]
|
||||
assert got["scaleBack"] is None
|
||||
assert got["drop"] == [["labor:A#0"], None]
|
||||
|
||||
Reference in New Issue
Block a user