feat(736-3): 단가산출 · 일위대가 [항목 추가] 기계(M01 EQ) 고름 — 고정 줄(이음 기계 · 단가 = 한 시간 노 · 재 · 경)

- 찾아 넣기 창 기계 줄 누름 = 서버 `/logic-lib/machine-hourly` 로 한 시간 값을 받아 고정 줄 단가로
- 일위대가 = 이음 기계 고정 줄 · 단가산출 = 더한줄 고정 줄(수량 1) — 같은 금액
- 서버 `M02_Machine_Hourly`(기계경비 로직 짝 차례 첫째 · 읽기만) · 시험 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 18:44:12 +09:00
co-authored by Claude Sonnet 5.5
parent aa26860358
commit 5e2ac2ae79
6 changed files with 159 additions and 7 deletions
@@ -49,6 +49,8 @@ export interface M01Element {
규격: string;
단위: string;
값: number | null;
/** 기계(736-3) — 한 시간 노 · 재 · 경(서버 `/logic-lib/machine-hourly` · 고르는 순간 받음) */
단가?: Record<"노무비" | "재료비" | "경비", number>;
}
/** 고를 수 있는 종류 — logic = M01 단가산출 · 기초 로직 · labor = 인력 · material = 재료 · machine = 기계 · sk = M02 단가산출 · pre = 일위대가 */
@@ -80,6 +82,16 @@ export const logicHeads = (rows: StandardRow[]): StandardRow[] =>
/** 일위대가 고정 줄(705-1 이음 꼴) — 인력 = 노임 LB · 재료 = 자재 MT · 단가 = 지금 값(셈은 단가 책 값) */
export function fixedOf(e: M01Element): LaborFixed {
if (e.종류 === "기계") {
// 736-3 기계 = 이음 기계 EQ · 단가 = 한 시간 노 · 재 · 경(셈은 줄 단가 그대로)
return {
이름: e.이름,
규격: e.규격,
단위: e.단위,
단가: e.단가 ?? { 노무비: 0, 재료비: 0, 경비: 0 },
이음: { 노임: null, 자재: null, 기계: e.코드 },
};
}
const labor = e.종류 === "인력";
return {
이름: e.이름,
@@ -171,6 +183,8 @@ export function openFindPicker(opt: FindPickOptions): void {
rowClass: (i) => (shown[i]?.dim ? "is-dim" : null),
onRowClick: (i) => shown[i]?.go(),
});
const fail = (error: unknown): void =>
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error");
const take = (f: () => void) => (): void => {
close();
f();
@@ -199,6 +213,20 @@ export function openFindPicker(opt: FindPickOptions): void {
},
];
});
/** 기계 고름 — 한 시간 값을 받아 단가로(못 셈 = 까닭 알림 · 창 그대로) */
const hourly = async (e: M01Element): Promise<M01Element | null> => {
try {
const got = await call<{ ok: boolean; reason?: string } & Record<string, number>>(
`/logic-lib/machine-hourly?key=${encodeURIComponent(e.코드)}`,
);
if (!got.ok) throw new Error(got.reason ?? tc("Load_Failed"));
const { 노무비, 재료비, 경비, 계 } = got;
return { ...e, 값: 계, 단가: { 노무비, 재료비, 경비 } };
} catch (error) {
fail(error);
return null;
}
};
const elementLines = (kind: M01Element["종류"], items: PickItem[]): Line[] =>
items.map((m) => {
const e: M01Element = {
@@ -223,7 +251,11 @@ export function openFindPicker(opt: FindPickOptions): void {
구분: join(kind, m.구분, put ? "" : L("M02_FindElementLater")),
출처: `M01 ${m.ref}`,
dim: !put,
go: put ? take(() => put(e)) : () => showToast(L("M02_FindElementLater"), "warning"),
go: !put
? () => showToast(L("M02_FindElementLater"), "warning")
: kind === "기계"
? () => void hourly(e).then((x) => x && take(() => put(x))())
: take(() => put(e)),
};
});
const docLines = (rows: SangeunRow[], kind: string): Line[] =>
@@ -346,8 +378,6 @@ export function openFindPicker(opt: FindPickOptions): void {
search.input.focus();
draw();
const fail = (error: unknown): void =>
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error");
void Promise.all([fetchLibLogics(), fetchStandard().catch(() => null)])
.then(([l, s]) => {
logics = l;