Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Lost.ts
T
eomsangdonandClaude Opus 5.5 fe78849463 feat(729-3): 단가산출 못옮김 잇기 창 · 단위 성격 다른 후보 계수 칸 · 남은 줄 표 못옮김 단추(729-4 이음)
- 새 Hopyo_Lost: 못옮김 줄마다 찾기(노임 LB · 자재 MT) · 기계(EQ) · 우리에 없음(뺌) → 캐시 m02-hopyo-lost 모두를 [잇기 만들기] 한 번에(서버가 부를 때마다 그 블록 잇기 전부로 다시 셈) · 바탕 = 그 SK 문서 바탕(로직 · 입력값 · 환산)
- 단가산출 칸: 만든 줄 표 못옮김 열 · [잇기 (n)] · 만든 줄 모아 둠 · 고름 대기 후보 계수(성격 다른 단위) = 계수 칸 → 환산 {계수} · 729-5 남은못옮김 칸 미리 받음
- 안 이은 줄 표: 못옮김 남은 SK 를 쓰는 줄(단산 · SK · 못옮김) = [못옮김 잇기 (n)]
- 시험 test_729_3_hopyo_left_screen 소스 확인 더함(계수 · 잇기 모두 · 바탕)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:57:47 +09:00

179 lines
7.3 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Hopyo_Lost.ts
* 단가산출 못옮김 잇기 창(PLAN 729-3 · 729-4 이음 · 요구서 내역19 2장 3 · 4) — 실무 블록을 SK 로 옮길 때 우리 M01 에
* 바로 못 옮긴 엑셀 줄(노임 · 기계 · 자재)을 줄마다 M01 요소로 이음 → SK 더한줄(엑셀 수량) · [뺌] = 놓음(까닭 남김).
* · 고름 = 캐시 `m02-hopyo-lost:{층}:{주인}:{키}:{번}` `{id: 잇기}` — 서버는 부를 때마다 그 블록 잇기 전부로 다시 셈
* (앞 잇기 안 남음) → [잇기 만들기] 는 캐시 모두를 보냄 · 바탕 = 그 SK 문서 바탕(로직 · 입력값 · 환산 그대로)
* · 남은 못옮김 = 서버 답 그대로 표 · 0 이면 그 SK 를 쓰는 일위대가 줄이 이어짐(남으면 만들기 409)
* 계산은 서버 — 화면은 고름 · 보이기만 · 저장은 [잇기 만들기] 누를 때만.
* ========================================================================== */
import { createButton, el, showToast } from "@ui/ui_template_elements";
import { t as L } from "@ui/ui_template_locale";
import { openModal } from "@ui/ui_template_modal";
import { createTable } from "@ui/ui_template_table";
import { createText } from "@ui/ui_template_text";
import { readTemplate } from "./M02_MasterTemplete_Api_Fetch";
import { openFindPicker } from "./M02_MasterTemplete_Find_Pick";
import {
makeSangeun,
type HopyoScope,
type SangeunBuilt,
type SangeunLink,
type SangeunLost,
} from "./M02_MasterTemplete_Hopyo_Api";
import { pickMachine } from "./M02_MasterTemplete_Hopyo_Left";
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
const NONE = "우리에 없음";
export const lostKey = (scope: HopyoScope, key: string, no: number): string =>
`m02-hopyo-lost:${scope.layer}:${scope.owner ?? ""}:${key}:${no}`;
/** 캐시 잇기 `{id: 잇기}` */
export function loadLinks(k: string): Record<string, SangeunLink> {
try {
const got = JSON.parse(sessionStorage.getItem(k) ?? "{}") as unknown;
return got && typeof got === "object" ? (got as Record<string, SangeunLink>) : {};
} catch {
return {};
}
}
const saveLinks = (k: string, v: Record<string, SangeunLink>): void =>
sessionStorage.setItem(k, JSON.stringify(v));
/** 고른 것 글 — 키 또는 뺌 */
const linkText = (x: SangeunLink | undefined): string =>
!x ? "" : x.뺌 ? `${L("M02_LostDrop")} · ${x.뺌}` : (x.노임 ?? x.기계 ?? x.자재 ?? "");
export interface LostOptions {
scope: HopyoScope;
key: string;
/** 블록 번(단산N) */
번: number;
/** 그 블록 SK 키 — 바탕을 읽음 */
sk: string;
이름?: string;
lost: SangeunLost[];
/** 잇기 만든 뒤(서버 답) */
done?: (got: SangeunBuilt) => void;
}
/** 못옮김 잇기 창 */
export function openLost(opt: LostOptions): void {
const k = lostKey(opt.scope, opt.key, opt.번);
let lost = opt.lost;
openModal({
title: `${L("M02_LostTitle")} — 단산${opt.번} ${opt.이름 ?? ""}`.trim(),
closeLabel: L("M02_Close"),
width: "960px",
dialogClass: "m02-modal",
mount: (body, _close, actions) => {
const list = el("div", { attrs: { "data-lost": "" } });
const put = (id: string, link: SangeunLink | null): void => {
const all = loadLinks(k);
if (link) all[id] = link;
else delete all[id];
saveLinks(k, all);
draw();
};
const make = createButton({
label: L("M02_LostMake"),
variant: "filled",
onClick: () => void run(),
});
make.dataset.lostMake = "";
actions.append(make);
body.append(createText(L("M02_LostHint"), "hint", 14), list);
async function run(): Promise<void> {
const links = Object.values(loadLinks(k));
if (!links.length) return showToast(L("M02_WizSkPickFirst"), "warning");
make.disabled = true;
try {
const doc = (await readTemplate(opt.scope.layer, "sangeun", opt.sk, null, opt.scope.owner))
.문서 as { 바탕?: { 로직?: string } | null; 입력값?: Record<string, unknown>; 환산?: { 계수?: number } }; // prettier-ignore
const pick = {
...(doc.바탕?.로직 ? { 로직: doc.바탕.로직, 입력값: doc.입력값 ?? {} } : {}),
...(doc.환산?.계수 ? { 환산: { 계수: doc.환산.계수 } } : {}),
잇기: links,
};
const got = await makeSangeun(opt.scope, opt.key, { [opt.번]: pick });
lost = got.만듦.find((x) => x.번 === opt.번)?.못옮김 ?? [];
showToast(L("M02_LostLeft").replace("{n}", String(lost.length)), lost.length ? "warning" : "success"); // prettier-ignore
opt.done?.(got);
draw();
} catch (error) {
showToast(why(error), "error");
} finally {
make.disabled = false;
}
}
function draw(): void {
const all = loadLinks(k);
list.dataset.lost = String(lost.length);
const rows = lost.map((x) => {
const kind = x.갈래;
const find = createButton({
label: kind === "기계" ? L("M02_LeftMachine") : L("M02_LeftFind"),
size: 24,
variant: "ghost",
onClick: () =>
kind === "기계"
? pickMachine(x, (code) => put(x.id, { id: x.id, 기계: code }))
: openFindPicker({
layer: opt.scope.layer,
owner: opt.scope.owner,
title: `${x.이름} ${x.규격 ?? ""}`.trim(),
logic: () => showToast(L("M02_GridLinkNoLogic"), "info"),
element: (e) =>
e.종류 === (kind === "노임" ? "인력" : "재료")
? put(x.id, { id: x.id, [kind]: e.코드 })
: showToast(L("M02_LostKind").replace("{k}", kind), "warning"),
}),
});
const drop = createButton({
label: NONE,
size: 24,
variant: "ghost",
onClick: () => put(x.id, { id: x.id, 뺌: NONE }),
});
drop.title = L("M02_LostDropTip");
const clear = createButton({
label: L("M02_LostClear"),
size: 24,
variant: "link",
onClick: () => put(x.id, null),
});
return [
kind,
x.이름,
x.규격 ?? "",
`${x.수량 ?? ""} ${x.단위 ?? ""}`.trim(),
x.글 ?? "",
linkText(all[x.id]),
el("div", { className: "ui-toolbar", children: [find, drop, ...(all[x.id] ? [clear] : [])] }), // prettier-ignore
];
});
list.replaceChildren(
createTable(
[
{ label: L("M02_LeftKind"), width: "56px" },
{ label: L("M02_SangeunColName") },
{ label: L("M02_LeftSpec") },
{ label: L("M02_SangeunColQty"), width: "90px" },
{ label: L("M02_LeftWhy") },
{ label: L("M02_LeftPicked") },
{ label: L("M02_LeftPick"), width: "220px" },
],
rows,
{ density: "data", empty: L("M02_LostZero") },
),
);
}
draw();
},
});
}