- 부모 저장 때 확인 안 된 하위 호표(깊이 끝까지)를 하위 먼저 그 체크대로 함께 만듦(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>
131 lines
6.0 KiB
TypeScript
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("");
|
|
}
|