feat(M02): 일위대가 자재 줄 2행 · 가격 다섯(43-3) — M01 값 열 다섯(물가자료 · 유통물가 · 물가정보 · 거래가격 · 관급) · 기준단가 칸 강조
- 서버 calc 답 자재 줄에 가격 · 기준칸(마스터 · 회사 · 개인 층 calc · 프로젝트 복사본 calc 같은 함수 material_price) · 원본 자료는 읽기만 - 자재목록 표 = 코드 · 이름 · 규격 · 수량산출(위 1 단위 / 아래 지금 수량) · 단위 · 가격 다섯(빈 것 「—」) · 기준단가 / 반영 금액 - 화면 판정 시나리오 resources/tester/ui_checks/43-3_자재가격.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -325,11 +325,8 @@ def _reflected(snapshot: Any, labor: dict[str, Any], lines: list[dict], area: An
|
||||
mats = []
|
||||
for one in doc.get("자재") or []:
|
||||
code = str(one.get("코드") or "")
|
||||
try:
|
||||
price, _ = mm.element(snapshot, code, {}, one.get("단위") or None)
|
||||
except mf.FormulaError:
|
||||
price = None
|
||||
mats.append({"코드": code, "알약": labor_doc.material_key(code), "기준단가": price})
|
||||
got = labor_doc.material_price(snapshot, code, one.get("단위")) # 가격 다섯 · 기준칸(43-3)
|
||||
mats.append({"코드": code, "알약": labor_doc.material_key(code), **got})
|
||||
settled = labor_doc.settle(
|
||||
to_json({"줄": base, "자재": mats}), doc, labor_doc.area_values(area)
|
||||
)
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
수량 = `null`(면적산출 알약 칸 값 · 로직 알약 「이름|단위」 · 자재 알약 「자재|MT키」) | `{손: 수}`
|
||||
(손이 칸보다 앞섬) · 같은 로직 두 번 금지 · 입력값 = 그 로직 입력 이름만(빈 값 = 자동값).
|
||||
금액: 기준 = 결과단위 그대로(서버 단독 · M01 엔진) · 반영 = 기준 × 수량 ÷ 배 · 돈 아닌 로직은
|
||||
반영 없음 · 자재 기준 단가 = `master_material.element` 기본값.
|
||||
반영 없음 · 자재 기준 단가 = `master_material.element` 기본값 · 자재 줄에 값 열 다섯(`가격`) ·
|
||||
기준단가가 된 칸 이름(`기준칸`)도 실음(43-3).
|
||||
짝(CLAUDE.md 5장 ①): 수량 · 반영 · 합 `settle` ↔ `M02_MasterTemplete_Labor_Calc.ts` —
|
||||
거울 시험 `resources/tester/test_m02_labor_calc_mirror.py`.
|
||||
"""
|
||||
@@ -247,6 +248,24 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
|
||||
return _json({"줄": lines, "자재": mats, "기준합": _sum(bases), "반영합": _sum(reflects)})
|
||||
|
||||
|
||||
def material_price(master: Any, code: str, unit: str | None) -> dict[str, Any]:
|
||||
"""자재 한 줄 기준 — `{기준단가, 가격: {값 열 다섯}, 기준칸, 까닭}` · 기준단가 = `mm.element`
|
||||
(값 열 가운데 낮은 값) · 기준칸 = 그 값이 나온 열 이름(출처 `키.열`) · 원본 자료는 읽기만."""
|
||||
row = master.get(code) or {}
|
||||
prices = {slot: row.get(slot) for slot in mm.SLOTS}
|
||||
try:
|
||||
price, source = mm.element(master, code, {}, unit or None)
|
||||
except mf.FormulaError as e:
|
||||
return {"기준단가": None, "가격": prices, "기준칸": None, "까닭": str(e)}
|
||||
slot = str(source).split(".")[-1] if source else None
|
||||
return {
|
||||
"기준단가": price,
|
||||
"가격": prices,
|
||||
"기준칸": slot if slot in mm.SLOTS else None,
|
||||
"까닭": None if price is not None else "단가 없음",
|
||||
}
|
||||
|
||||
|
||||
def calc(doc: dict, given: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
"""`doc` = 정리한 문서 · `given` = {알약 키: 값}(없으면 지금 층 면적산출 파일에서) —
|
||||
줄마다 기준(서버 단독 · M01 엔진) → `settle`. 못 도는 줄은 까닭만(통째로 멈추지 않음)."""
|
||||
@@ -277,17 +296,14 @@ def calc(doc: dict, given: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
lines.append({**line, "결과": got["결과"], "기준": None, "까닭": None})
|
||||
continue
|
||||
lines.append({**line, "기준": {k: got[k] for k in (*COSTS, "계")}, "까닭": None})
|
||||
mats = []
|
||||
for one in doc.get("자재") or []:
|
||||
try:
|
||||
price, _ = mm.element(whole, one["코드"], {}, one.get("단위") or None)
|
||||
except mf.FormulaError as e:
|
||||
price, why = None, str(e)
|
||||
else:
|
||||
why = None if price is not None else "단가 없음"
|
||||
mats.append(
|
||||
{"코드": one["코드"], "알약": material_key(one["코드"]), "기준단가": price, "까닭": why}
|
||||
)
|
||||
mats = [
|
||||
{
|
||||
"코드": one["코드"],
|
||||
"알약": material_key(one["코드"]),
|
||||
**material_price(whole, one["코드"], one.get("단위")),
|
||||
}
|
||||
for one in doc.get("자재") or []
|
||||
]
|
||||
have = _area_of(doc.get("열")) if given is None else given
|
||||
return settle(_json({"줄": lines, "자재": mats}), doc, have)
|
||||
|
||||
|
||||
@@ -58,6 +58,13 @@
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* 자재 가격 다섯 — 기준단가가 된 칸(43-3) */
|
||||
.m02-labrow__price.is-base-price {
|
||||
color: var(--color-accent);
|
||||
font-weight: 700;
|
||||
box-shadow: inset 0 -2px 0 var(--color-accent);
|
||||
}
|
||||
|
||||
.m02-labrow__applied {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ export interface CalcMaterial {
|
||||
코드: string;
|
||||
알약?: string | null;
|
||||
기준단가: number | null;
|
||||
/** M01 자재품목 값 열 다섯(물가자료 · 유통물가 · 물가정보 · 거래가격 · 관급 · 빈 값 null · 43-3) */
|
||||
가격?: Record<string, number | null>;
|
||||
/** 기준단가가 된 열 이름 — 없으면 null */
|
||||
기준칸?: string | null;
|
||||
수량?: number | null;
|
||||
수량출처?: QtySource;
|
||||
반영?: number | null;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* 일위대가 담은 로직 표 · 자재목록 표(39-3) — 상세 · 보기 갈래(면적산출 오른쪽 패널 · 카드)가 같이 씀.
|
||||
* 담은 로직 = 차례 · 로직(키 · 이름 + 작은 글 「지역 서울 · 규격 25mm · 수량 B6 = 12.5㎡」 · 「빈 값 n」 붉게) ·
|
||||
* 결과 단위 · 금액 칸 넷(노무비 · 재료비 · 경비 · 합계 · 칸마다 위 기준 작게 / 아래 반영) · 맨 아래 합계 줄.
|
||||
* 자재목록 = 코드 · 이름 · 규격 · 단위 · 수량 · 기준 단가 · 반영 금액. 금액은 calc 답(없으면 「…」).
|
||||
* 자재목록 = 코드 · 이름 · 규격 · 수량산출 · 단위 · 가격 다섯(기준 칸 강조) · 기준단가 / 반영 금액(43-3).
|
||||
* 금액은 calc 답(없으면 「…」).
|
||||
* 줄 누름 · 순서 단추는 `on` 이 있을 때만(보기 갈래 = 없음).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -278,7 +279,12 @@ export interface MatTableData {
|
||||
cells?: ReadonlyMap<string, string>;
|
||||
}
|
||||
|
||||
/** 자재목록 표 — 보기 갈래는 `on` 없이 */
|
||||
/** M01 자재품목 값 열 다섯(이름 그대로 · 43-3) — 서버 calc 답 `가격` 열쇠와 같음 */
|
||||
export const PRICE_SLOTS = ["물가자료", "유통물가", "물가정보", "거래가격", "관급"] as const;
|
||||
|
||||
/** 자재목록 표(43-3 · 로직 줄과 같은 꼴) — 코드 · 이름 · 규격 · 수량산출(위 기준 수량 1 단위 / 아래 지금
|
||||
* 수량) · 단위 · 가격 다섯(빈 것 「—」 · 기준단가가 된 칸 강조) · 금액(위 기준단가 / 아래 반영 = 기준단가 × 수량) ·
|
||||
* 보기 갈래는 `on` 없이 */
|
||||
export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HTMLElement {
|
||||
const wait = !data.calc && !data.fail;
|
||||
const remove = on?.remove;
|
||||
@@ -286,7 +292,20 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
wait ? "…" : v === null || v === undefined ? "—" : formatMoney(v);
|
||||
const trs = data.mats.map((mat, i) => {
|
||||
const line: LaborCalcMat | undefined = data.calc?.자재.find((m) => m.코드 === mat.코드);
|
||||
const qty = line?.수량 === null || line?.수량 === undefined ? "" : num(line.수량);
|
||||
// 수량산출 칸은 로직 줄 칸을 그대로 씀 — 기준 수량 = 자재 단위 1
|
||||
const qtyLine = { ...line, 배: 1, 분모: mat.단위 ?? "" } as LaborCalcLine;
|
||||
const priceCell = (slot: (typeof PRICE_SLOTS)[number]): HTMLElement => {
|
||||
const base = !!line?.기준칸 && line.기준칸 === slot;
|
||||
return el("td", {
|
||||
className: `m02-labrow__num m02-labrow__price${base ? " is-base-price" : ""}`,
|
||||
text: show(line?.가격?.[slot]),
|
||||
attrs: {
|
||||
"data-label": slot,
|
||||
"data-price-slot": slot,
|
||||
...(base ? { title: tc("Labor_BasePrice") } : {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
const tr = el("tr", {
|
||||
className: on ? "m02-labrow__row is-open" : "m02-labrow__row",
|
||||
attrs: { "data-labor-mat": mat.코드 },
|
||||
@@ -302,29 +321,14 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
],
|
||||
}),
|
||||
el("td", { className: "m02-labrow__spec", text: mat.규격 ?? "" }),
|
||||
qtyCell(qtyLine, data.cells, on?.qty ? () => on.qty?.(i) : null),
|
||||
el("td", {
|
||||
className: "m02-labrow__unit",
|
||||
text: mat.단위 ?? "",
|
||||
attrs: { "data-label": tc("Labor_Unit") },
|
||||
}),
|
||||
el("td", {
|
||||
className: "m02-labrow__num",
|
||||
text: qty,
|
||||
attrs: {
|
||||
title: qtyText(line, mat.단위 ?? "", data.cells),
|
||||
"data-label": tc("Labor_Qty"),
|
||||
},
|
||||
}),
|
||||
el("td", {
|
||||
className: "m02-labrow__num",
|
||||
text: show(line?.기준단가),
|
||||
attrs: { "data-label": tc("Labor_BasePrice") },
|
||||
}),
|
||||
el("td", {
|
||||
className: "m02-labrow__num",
|
||||
text: show(line?.반영),
|
||||
attrs: { "data-label": tc("Labor_AppliedSum") },
|
||||
}),
|
||||
...PRICE_SLOTS.map(priceCell),
|
||||
moneyCell(line?.기준단가, line?.반영, wait, tc("Labor_AppliedSum")),
|
||||
...(remove
|
||||
? [
|
||||
el("td", {
|
||||
@@ -351,10 +355,10 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
tc("Labor_Code"),
|
||||
tc("Col_Name"),
|
||||
tc("Labor_Spec"),
|
||||
tc("Labor_QtyCol"),
|
||||
tc("Labor_Unit"),
|
||||
tc("Labor_Qty"),
|
||||
tc("Labor_BasePrice"),
|
||||
tc("Labor_AppliedSum"),
|
||||
...PRICE_SLOTS,
|
||||
`${tc("Labor_BasePrice")} · ${tc("Labor_AppliedSum")}`,
|
||||
...(remove ? [tc("Col_Move")] : []),
|
||||
];
|
||||
return data.mats.length
|
||||
|
||||
Reference in New Issue
Block a user