Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Kids.ts
T
eomsangdonandClaude Opus 5.5 eaa8064f1a feat(777-1): 호표가 다른 호표를 부를 때 = 일위대가 둘 — 하위 함께 만들기 · 같은 SC 재사용 · 결과 글 · 납작 길 막음
- 부모 저장 때 확인 안 된 하위 호표(깊이 끝까지)를 하위 먼저 그 체크대로 함께 만듦(409 「먼저 확인」 걷음)
  · 서버 = 체크 제안 몸(M02_Hopyo_Kids.proposal ↔ 화면 bodyFor 짝) · 화면 = 확인 창 캐시 고름(Hopyo_Kids.ts)
  · 하위 실패 = 409 「하위 제N호표 실패 — …」 · 하위 · 하위남은줄 → 결과 표 [고르기]가 그 하위를 엶
  · [모두 고정] 연쇄 실패 까닭 따로 · 미리 보기는 하위 호표 줄을 이음으로 셈
- 같은 엑셀 다시 불러오기 = 같은 이름 · 같은 내용(수량까지) SC 재사용 — 본뜬.또(SK 꼴) · 끝내기는 그 몫만 뺌
- 결과 표 글 「SC-12 · 하위 제4호표 SC-11(사본 담음)」 · 확인 창 호표 줄 글 「하위 일위대가 SC-11 사본」
- [구조물 조합으로] 도 하위 먼저(못 만들면 지금처럼 고정 줄) · 붙여넣기 · B09 통째 = 이미 하위 먼저(안 고침)
- 엑셀 대조(CUTS · _excel_round · excel_given · compare) → 새 M02_Hopyo_Compare.py(Build 608 → 540)
- M02_Labor_Router 병행 흔적 가드 걷음(_later · inspect · 몸 칸 새로)
- STmate [엑셀 보기] 단가산출근거로 감(형식별 블록 시트 이름)
- 낡은 시험 고침: 「제46호표 먼저 확인」 409 · 글 → 하위 함께(777-1 정함) · 718-1 실패 = 남은줄 또는 하위 ·
  화면 시험 StaleError 안내 → unmadeKids · leftAt · 새 지킴 시험 test_777_1_hopyo_kids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:24:48 +09:00

131 lines
6.0 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Hopyo_Kids.ts
* 일위대가 실무자료 만들기 몸 · 하위 호표 함께(PLAN 777-1 · 요구 621-4) — 호표가 다른 호표를 부르면 일위대가 둘
* (하위 일위대가 따로 + 부모 줄 = 하위 사본).
* · bodyFor · wholeFor = 확인 창 캐시 위 체크 제안(짝 = 서버 `M02_Hopyo_Kids.proposal` · 캐시 빈 것)
* · unmadeKids = 고른 호표 줄이 부르는 확인 안 된 하위 호표(깊이 끝까지 · 하위 먼저) — [저장]이 그 캐시대로 함께 만듦
* · buildWithKids = 확인 창 [생성] — 확인 안 된 하위를 그 캐시대로 먼저 → 부모(실패 = 「하위 제N호표 실패」)
* · 캐시 없는 하위도 서버가 부모 저장 때 제안대로 함께 만듦(`M02_Hopyo_Kids.first`) — 화면은 캐시 고름을 살림
* · kidsText = 결과 글 「· 하위 제4호표 SC-11(사본 담음)」 · leftAt = 하위 실패의 남은 줄 = 그 하위 [고르기]
* ========================================================================== */
import { t as L } from "@ui/ui_template_locale";
import { StaleError } from "./M02_MasterTemplete_Api_Fetch";
import {
buildHopyo,
readHopyoOne,
type HopyoBuilt,
type HopyoOne,
type HopyoScope,
} from "./M02_MasterTemplete_Hopyo_Api";
import { bodyOf, draftKey, loadDraft, partsOf, toTarget } from "./M02_MasterTemplete_Hopyo_Check";
type Body = Parameters<typeof buildHopyo>[2];
type Whole = Parameters<typeof buildHopyo>[3];
type From = Parameters<typeof buildHopyo>[1];
/** 호표 하나의 몸 — 확인 창 캐시 위 체크 제안 · 통째면 줄 없음 */
export function bodyFor(scope: HopyoScope, key: string, one: HopyoOne): Body {
const h = one.호표;
const d = loadDraft(draftKey(scope, key, h.번));
return h.통째 && (d.통째 ?? h.통째.체크) ? [] : bodyOf(h.줄, d);
}
export function wholeFor(scope: HopyoScope, key: string, one: HopyoOne): Whole {
const h = one.호표;
const d = loadDraft(draftKey(scope, key, h.번));
return h.통째 && (d.통째 ?? h.통째.체크) ? { 대상: h.통째.대상.map(toTarget) } : null;
}
/** (줄, 통째) 몸 한 벌 */
export const bodyPair = (scope: HopyoScope, key: string, one: HopyoOne): [Body, Whole] => [
bodyFor(scope, key, one),
wholeFor(scope, key, one),
];
/** 호표 줄이 부르는 호표 번(체크 안 한 호표 줄 · 캐시에서 고른 호표 대상까지) */
const kidNos = (scope: HopyoScope, key: string, one: HopyoOne): number[] => {
const d = loadDraft(draftKey(scope, key, one.호표.번));
return one.호표.줄.flatMap((l) =>
partsOf(l, d).flatMap((p) => (p.호표 !== undefined ? [p.호표] : [])),
);
};
/** [저장] 할 호표 — 함께 만드는 하위 · 체크 줄(통째) 있음 · 하위를 부름(호표 줄 = 하위 사본) */
export function readyFor(scope: HopyoScope, key: string, one: HopyoOne, picked: number[]): boolean {
const [lines, whole] = bodyPair(scope, key, one);
return (
!picked.includes(one.호표.번) || !!lines.length || !!whole || !!kidNos(scope, key, one).length
);
}
/** 확인 안 된 하위 호표 보기 — 하위 먼저 · `ones` 와 이미 만든 하위(그 아래도) 뺌 */
export async function unmadeKids(
scope: HopyoScope,
key: string,
target: HopyoScope,
ones: HopyoOne[],
): Promise<HopyoOne[]> {
const out: HopyoOne[] = [];
const seen = new Set(ones.map((one) => one.호표.번));
async function visit(one: HopyoOne): Promise<void> {
for (const no of kidNos(scope, key, one)) {
if (seen.has(no)) continue;
seen.add(no);
const kid = await readHopyoOne(scope, key, no, target);
if (kid.호표.만든) continue;
await visit(kid);
out.push(kid);
}
}
for (const one of ones) await visit(one);
return out;
}
/** 하위 실패 — 까닭 「하위 제N호표 실패 — …」 · 남은 줄은 그 하위 몫(`leftAt`) */
function kidFail(error: unknown, no: number): StaleError {
const left = (error as { detail?: { 남은줄?: unknown } } | null)?.detail?.남은줄;
const why = error instanceof Error ? error.message : String(error);
const text = `${L("M02_HopyoKidFail").replace("{n}", String(no))} — ${why}`;
return new StaleError(text, { 까닭: text, 하위: no, 하위남은줄: left ?? [] });
}
/** 확인 창 [생성] — 확인 안 된 하위를 그 캐시대로 먼저 만들고 부모 */
export async function buildWithKids(
target: HopyoScope,
from: From,
줄: Body,
통째?: Whole,
): Promise<HopyoBuilt> {
const scope = { layer: from.층, owner: from.주인 };
const me = await readHopyoOne(scope, from.키, from.번, target);
for (const kid of await unmadeKids(scope, from.키, target, [me])) {
const no = kid.호표.번;
const body = [bodyFor(scope, from.키, kid), wholeFor(scope, from.키, kid)] as const;
await buildHopyo(target, { ...from, 번: no }, ...body).catch((error: unknown) => {
throw kidFail(error, no);
});
}
return buildHopyo(target, from, 줄, 통째);
}
/** 하위 실패면 (그 하위 남은 줄 몸, 하위 번) · 아니면 그대로 — 결과 표 [고르기]가 그 하위를 엶 */
export function leftAt(error: unknown, no: number): [unknown, number] {
const d = (error as { detail?: { 하위?: number; 하위남은줄?: unknown } } | null)?.detail;
return d?.하위 !== undefined ? [{ detail: { 남은줄: d.하위남은줄 } }, d.하위] : [error, no];
}
/** 결과 글 꼬리 — 같은 내용 재사용 · 하위 일위대가(사본 담음 · 함께 만듦) */
export function kidsText(got: HopyoBuilt): string {
const more = got as HopyoBuilt & {
재사용?: boolean;
하위?: { 번: number; 키: string | null; 새로?: boolean }[];
};
const kids = (more.하위 ?? []).map((k) => {
const head = L("M02_HopyoKid")
.replace("{n}", String(k.번))
.replace("{key}", k.키 ?? "");
return ` · ${head}${k.새로 ? ` · ${L("M02_HopyoKidNew")}` : ""}`;
});
return (more.재사용 ? ` · ${L("M02_HopyoReused")}` : "") + kids.join("");
}