- 판정 = 자체 로직 편집 · 시험 값 고침 · M02 줄 덮음 · 비목 박스 소계 모두 수식 보기로 됨(살릴 것 없음) - M01 상세: 숨은 단가산출 상세 덩이 · 자체 로직 시트 편집기 · 시험 고침 · 셈 구동(createDrive) · 보기 이름표(View.ts) · data-view CSS 걷음 - M02 불러오기 모달 머리 [항목 추가] · [율 줄 추가] 단추와 그 창(Rate.ts · openSangeunLinePicker) 걷음 - 글 정리 = Info_Own 새 뜻 · 죽은 글 키 · No_Key · 항목 추가 글 걷음 - 낡은 시험 고침 = 722(시험 고침 · 덮기 셈) · 799-2 · 804-2 · 804-6 · 804-7 · 806-1 · 717-2b · 717-4 · 721 · 728-2 — 걷은 것이 없음 확인으로 - 남김 = M02 수식 문서 없는 줄의 단가산출 상세 몸(cover · buildHo) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
77 lines
3.2 KiB
TypeScript
77 lines
3.2 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_Logic_Drive.ts
|
|
* 셈 구동(PLAN 799-2) — 화면 없이 「입력값 → 서버 셈 → 답」을 돌림. 시험 계산 칸(`Logic_Calc`)이 씀
|
|
* `driveRun` = 한 번 셈(`/calc` + `/text`) · `autoFill` = 자동값 채움 — 로직 상세 구동은 840-1 에 걷음
|
|
* 셈 자리 = 서버(CLAUDE.md 5 장 ③) · 화면 · sessionStorage 없음 · 고른 재료(`CalcPick`) 는 값 객체에 딸림
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
fetchAuto,
|
|
runCalc,
|
|
runText,
|
|
type CalcAnswer,
|
|
type CalcLine,
|
|
type LogicRow,
|
|
type TextAnswer,
|
|
} from "./M01_MasterData_UI_Logic_Api";
|
|
import { pickBody } from "./M01_MasterData_UI_Logic_CalcPick";
|
|
|
|
export interface DriveContext {
|
|
/** 저장된 키 — 새 로직은 null */
|
|
savedKey: string | null;
|
|
/** 저장 전 새 초안이면 비움 — 서버가 임시 자리에 얹어 셈 */
|
|
file?: string;
|
|
row: LogicRow;
|
|
dirty: () => boolean;
|
|
/** 로직마다 넣은 값 — 다시 그려도 남음 */
|
|
values: Record<string, string>;
|
|
onLines?: (lines: CalcLine[] | null) => void;
|
|
/** 읽는 식 줄(`/text`)도 같이 받고 싶을 때 — 호출이 `onLines` 보다 먼저 */
|
|
onText?: (answer: TextAnswer) => void;
|
|
}
|
|
|
|
/** 칸에 든 값 → 엔진 입력 — 빈 칸은 안 보냄(엔진이 「입력 없음」 으로 멈춤) · 고르기는 원래 값 그대로 */
|
|
function inputsOf(row: LogicRow, values: Record<string, string>): Record<string, unknown> {
|
|
const inputs: Record<string, unknown> = {};
|
|
for (const spec of row.입력 ?? []) {
|
|
const raw = (values[spec.이름] ?? "").trim();
|
|
if (raw === "") continue;
|
|
const option = spec.고르기?.find((o) => String(o) === raw); // 글 "35" 로 보내면 「고르기 밖」
|
|
inputs[spec.이름] =
|
|
option !== undefined ? option : Number.isNaN(Number(raw)) ? raw : Number(raw);
|
|
}
|
|
return inputs;
|
|
}
|
|
|
|
const draftBody = (ctx: DriveContext): { row?: LogicRow; file?: string } =>
|
|
ctx.dirty() || ctx.savedKey === null ? { row: ctx.row, file: ctx.file } : {};
|
|
|
|
/** 한 번 셈 — 답을 `onText` → `onLines` 차례로 알리고 돌려줌(`draft` = 저장 전 고침으로 셈) */
|
|
export async function driveRun(
|
|
ctx: DriveContext,
|
|
inputs = inputsOf(ctx.row, ctx.values),
|
|
): Promise<{ answer: CalcAnswer; draft: boolean }> {
|
|
const draft = ctx.dirty() || ctx.savedKey === null;
|
|
const body = {
|
|
key: ctx.savedKey ?? ctx.row.키,
|
|
inputs,
|
|
...pickBody(ctx.values),
|
|
...draftBody(ctx),
|
|
};
|
|
const answer = await runCalc(body);
|
|
if (ctx.onText) ctx.onText(await runText(body));
|
|
ctx.onLines?.(answer.ok ? (answer.lines ?? null) : null);
|
|
return { answer, draft };
|
|
}
|
|
|
|
/** 자동값으로 빈 칸 채움 — 채웠으면 true */
|
|
export async function autoFill(ctx: DriveContext): Promise<boolean> {
|
|
if (ctx.savedKey === null) return false;
|
|
const auto = await fetchAuto(ctx.savedKey);
|
|
const names = (ctx.row.입력 ?? [])
|
|
.map((spec) => spec.이름)
|
|
.filter((n) => auto[n] !== undefined && auto[n] !== null && !(ctx.values[n] ?? "").trim());
|
|
for (const n of names) ctx.values[n] = String(auto[n]);
|
|
return names.length > 0;
|
|
}
|