Merge origin/sub_laptop_7 into sub_laptop_8 — PLAN 704-4 규격 표 1차 · M02 일위대가 셈 짝 머리 설명 겹침 양쪽 살림(705-1 고정 줄 · 704-4 규격 열)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 14:44:09 +09:00
co-authored by Claude Opus 5.5
21 changed files with 955 additions and 32 deletions
@@ -6,7 +6,8 @@
* 수량(손이 칸보다 앞섬 · 칸 = 알약 키 값) · 반영 = 기준 × 수량 ÷ 배 · 자재 = 재료비만.
* 별도계상 자재(222-1 · 빈 칸 = 기본 별도계상) = 반영은 보이되 합에 안 더함(짝 `apart`).
* 참조 줄(단가승격 · 사전조합 · 50-3)은 `lineId` = 「참조|층|주인|키」(짝 `M02_Labor_Ref.ref_id`) ·
* 고정 줄(705-1)은 「고정|이름|규격|단위」(짝 `M02_Labor_Fixed.fixed_id`) · 기준 = 서버가 낸 줄 단가. */
* 고정 줄(705-1)은 「고정|이름|규격|단위」(짝 `M02_Labor_Fixed.fixed_id`) · 기준 = 서버가 낸 줄 단가.
* 규격 열(704-4) = 문서 `규격` · 그 열 수량이 있는 줄 = `{손}` 자리(`atSpec` · 짝 `M02_Labor_Spec.at`). */
import type { CalcLine as LogicLine } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
import type { LogicMat } from "./M02_Labor_LogicMats_Model";
@@ -91,10 +92,19 @@ export interface LaborCalc {
반영합?: Money;
}
/** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 */
/** 규격 열(704-4) — 이름(H · Φ) · 열(화면 차례) · 기본 · 줄 id 마다 열 수량 */
export interface LaborSpec {
이름: string;
열: string[];
기본: string;
수량: Record<string, Record<string, number>>;
}
/** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 · 규격 열 */
export interface LaborQtyDoc {
담은로직?: { 로직?: string; 참조?: LaborRef; 고정?: LaborFixed; 수량?: Qty }[];
자재?: { 코드: string; 수량?: Qty; 별도계상?: string }[];
규격?: LaborSpec | null;
}
function num(raw: unknown): number | null {
@@ -116,6 +126,37 @@ export function lineId(one: {
return one.로직;
}
/** 쓸 규격 열 — 고른 열이 있으면 그것 · 없으면 기본 · 규격 없음 = null(짝 `M02_Labor_Spec.column`) */
export function specColumn(doc: LaborQtyDoc, col?: string | null): string | null {
const cols = doc.규격?.열 ?? [];
if (!cols.length) return null;
return col && cols.includes(col) ? col : doc.규격?.기본 || cols[0];
}
/** 열 끼우기 — 그 열 규격 수량이 있는 줄 · 자재의 수량을 `{손}` 으로(짝 `M02_Labor_Spec.at`) */
export function atSpec<D extends LaborQtyDoc>(doc: D, col?: string | null): D {
const use = specColumn(doc, col);
if (use === null) return doc;
const qty = doc.규격?.수량 ?? {};
const put = <T extends { 수량?: Qty }>(one: T, id: string | undefined): T => {
const v = id !== undefined ? qty[id]?.[use] : undefined;
return v !== undefined && v !== null ? { ...one, 수량: { 손: v } } : one;
};
return {
...doc,
담은로직: (doc.담은로직 ?? []).map((one) => put(one, lineId(one))),
자재: (doc.자재 ?? []).map((one) => put(one, one.코드)),
};
}
/** 셈 답에서 칸 값 되살림 `{알약: 수량}` — 열마다 다시 셀 때(짝 `M02_Labor_Spec.given_of`) */
export function givenOf(result: LaborCalc): Record<string, number> {
const out: Record<string, number> = {};
for (const r of [...result.줄, ...result.자재])
if (r.수량출처 === "칸" && r.알약 && r.수량 != null) out[r.알약] = r.수량;
return out;
}
function zero(): Money {
return { 노무비: 0, 재료비: 0, 경비: 0, 계: 0 };
}
@@ -139,12 +180,15 @@ export function reflect(base: Money, qty: number | null, times: number): Money |
return out;
}
/** 기준이 선 계산 답 + 문서 손 값 + 알약 값(`{키: 값}`) → 수량 · 반영 · 합을 새로 채운 답 */
/** 기준이 선 계산 답 + 문서 손 값 + 알약 값(`{키: 값}`) → 수량 · 반영 · 합을 새로 채운 답 ·
* `col` = 규격 열(없으면 기본 · 704-4) */
export function settle(
result: LaborCalc,
doc: LaborQtyDoc,
whole: LaborQtyDoc,
given: Record<string, unknown>,
col?: string | null,
): LaborCalc {
const doc = atSpec(whole, col);
const have: Record<string, number> = {};
for (const [k, raw] of Object.entries(given)) {
const v = num(raw);