- labor 종류 · 일위대가번 SU(도번 · 면적번과 같은 뒷자리) · 빈 문서 · 뼈대 검사
- M02_Labor_Doc: 정리 · 검사(같은 로직 두 번 · 없는 로직 · 자재 · 수량 꼴) · 기준(M01 엔진) · settle(수량 · 반영 · 합)
- [저장] · 복사 때 서버가 계산값 다시 적음 · 면적산출 저장 때 그 열 일위대가 계산값도
- 짝 M02_MasterTemplete_Labor_Calc.ts settle + 거울 시험
- 길 POST /layers/{층}/labor/{열}/calc · GET …/pills (main.py 등록)
- M01 _PER 「원/1,000본」 배 1000
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
/* M02 일위대가 수량 · 반영 · 합(PLAN 39-1a · 설계_정함 1장) — 면적 칸 값이 바뀌면 화면이 바로 곱함.
|
|
*
|
|
* 짝(CLAUDE.md 5장 ①): `M02_Labor_Doc.py` `settle` 과 같은 규칙 —
|
|
* 거울 시험 `resources/tester/test_m02_labor_calc_mirror.py`.
|
|
* 기준(결과단위 그대로)은 서버 calc 길이 셈 · 여기는 받은 기준에
|
|
* 수량(손이 칸보다 앞섬 · 칸 = 알약 키 값) · 반영 = 기준 × 수량 ÷ 배 · 자재 = 재료비만. */
|
|
|
|
export const MONEY_KEYS = ["노무비", "재료비", "경비", "계"] as const;
|
|
export type Money = Record<(typeof MONEY_KEYS)[number], number>;
|
|
export type QtySource = "손" | "칸" | "없음";
|
|
/** 문서의 수량 칸 — null = 면적산출 알약 · {손} = 손 입력 */
|
|
export type Qty = { 손: number } | null;
|
|
|
|
export interface CalcLine {
|
|
로직: string;
|
|
알약?: string | null;
|
|
분모?: string | null;
|
|
배: number;
|
|
기준: Money | null;
|
|
수량?: number | null;
|
|
수량출처?: QtySource;
|
|
반영?: Money | null;
|
|
까닭?: string | null;
|
|
결과?: unknown;
|
|
}
|
|
|
|
export interface CalcMaterial {
|
|
코드: string;
|
|
알약?: string | null;
|
|
기준단가: number | null;
|
|
수량?: number | null;
|
|
수량출처?: QtySource;
|
|
반영?: number | null;
|
|
까닭?: string | null;
|
|
}
|
|
|
|
export interface LaborCalc {
|
|
줄: CalcLine[];
|
|
자재: CalcMaterial[];
|
|
기준합?: Money;
|
|
반영합?: Money;
|
|
}
|
|
|
|
/** 수량만 보는 문서 조각 — 담은 로직 · 자재의 손 값 */
|
|
export interface LaborQtyDoc {
|
|
담은로직?: { 로직: string; 수량?: Qty }[];
|
|
자재?: { 코드: string; 수량?: Qty }[];
|
|
}
|
|
|
|
function num(raw: unknown): number | null {
|
|
if (raw === null || raw === undefined || typeof raw === "boolean") return null;
|
|
const n = Number(String(raw).replace(/,/g, "").trim());
|
|
return String(raw).trim() !== "" && Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
function zero(): Money {
|
|
return { 노무비: 0, 재료비: 0, 경비: 0, 계: 0 };
|
|
}
|
|
|
|
function pick(
|
|
hand: Qty | undefined,
|
|
key: string | null | undefined,
|
|
given: Record<string, number>,
|
|
): [number | null, QtySource] {
|
|
const own = hand ? num(hand.손) : null;
|
|
if (own !== null) return [own, "손"];
|
|
if (key && key in given) return [given[key], "칸"];
|
|
return [null, "없음"];
|
|
}
|
|
|
|
/** 반영 = 기준 × 수량 ÷ 배 — 수량이 없으면 null */
|
|
export function reflect(base: Money, qty: number | null, times: number): Money | null {
|
|
if (qty === null) return null;
|
|
const out = zero();
|
|
for (const k of MONEY_KEYS) out[k] = (base[k] * qty) / times;
|
|
return out;
|
|
}
|
|
|
|
/** 기준이 선 계산 답 + 문서 손 값 + 알약 값(`{키: 값}`) → 수량 · 반영 · 합을 새로 채운 답 */
|
|
export function settle(
|
|
result: LaborCalc,
|
|
doc: LaborQtyDoc,
|
|
given: Record<string, unknown>,
|
|
): LaborCalc {
|
|
const have: Record<string, number> = {};
|
|
for (const [k, raw] of Object.entries(given)) {
|
|
const v = num(raw);
|
|
if (v !== null) have[k] = v;
|
|
}
|
|
const hands = new Map((doc.담은로직 ?? []).map((one) => [one.로직, one.수량 ?? null]));
|
|
const mhands = new Map((doc.자재 ?? []).map((one) => [one.코드, one.수량 ?? null]));
|
|
const base = zero();
|
|
const done = zero();
|
|
const add = (sum: Money, m: Money | null): void => {
|
|
if (m) for (const k of MONEY_KEYS) sum[k] += m[k];
|
|
};
|
|
const 줄 = result.줄.map((line) => {
|
|
const [qty, source] = pick(hands.get(line.로직), line.알약, have);
|
|
const own = line.기준 ? reflect(line.기준, qty, num(line.배) || 1) : null;
|
|
add(base, line.기준);
|
|
add(done, own);
|
|
return { ...line, 수량: qty, 수량출처: source, 반영: own };
|
|
});
|
|
const 자재 = result.자재.map((one) => {
|
|
const [qty, source] = pick(mhands.get(one.코드), one.알약, have);
|
|
const price = num(one.기준단가);
|
|
const cost = price !== null && qty !== null ? price * qty : null;
|
|
if (price !== null) add(base, { ...zero(), 재료비: price, 계: price });
|
|
if (cost !== null) add(done, { ...zero(), 재료비: cost, 계: cost });
|
|
return { ...one, 수량: qty, 수량출처: source, 반영: cost };
|
|
});
|
|
return { 줄, 자재, 기준합: base, 반영합: done };
|
|
}
|