Files
Aislo/A00_Common/line_stack_material.ts
T
eomsangdonandClaude Opus 5.5 520bb33909 feat(827-3): 불러오기 재료 걸음 3 — 고를 재료 판 · 그 파일 안 고름(별칭 아님) · SK 상세 같은 파일 물음
- 새 M02_San_MatPick: 종 열쇠(이름 · 규격 · 단위 · 단서) · 고름 자리 = 개인 층 실무자료/재료고름/<파일 지문>.json([끝내기] 뒤 남음 · 같은 파일 다시 올리면 그대로) · 판(블록마다 재료찾기 판정 → 종 · 첫 판 한 번 셈 → 산근 저장본 칸) · 만들 때 얹기 · 이미 만든 SK 얹기(가져온 그대로인 줄만 · 바꿈 · 따로 고른 줄 그대로 · 풀면 가져온 그대로)
- 만들기: from-hopyo 가 그 파일 고름째 만듦(저장 안 한 판 고름 = 만들 때 저장) · 원본 지문 · 고른 줄 = 이음 + 재료찾기.고름 · 문서 셈 우리 값(엑셀 값 기억 없음) · 별도계상 = 품목만(줄 이음 · 단가 0 그대로)
- 문서 머리 가져온.재료(판정 · 후보 · 단서 · 처음 연결) · 가져온.지문
- 길: GET/POST hopyo-materials/{HP}(판 · [저장] · 만든 것 적용은 물음 예일 때만) · GET/POST material-picks/{지문}(SK 상세 물음)
- 화면: 단가산출 목록 위 「고를 재료」 판(후보 고름 · 찾기 · 고름 풀기 · n 블록에 적용 · 이어진 재료 접힘 · [저장]) · 저장 안 한 고름 = 떠날 때 물음 · 줄 쌓기 재료 줄 판정 배지 · 풍선말(판정 · 단서 · 후보) · 이름 단추 · [이음 고르기] 뒤 「같은 파일 n 줄」 물음
- 별도계상 설명 = 「별도계상 · 품목: 이름 규격」(엔진 이은 품목은 자재표 이름) — test_827_2 기대 글 고침(품목 이음 → 품목 이름)
- 시험 test_827_3 셋(파일 고름 블록 둘 · 다른 파일 0 · 별칭 · 마스터 그대로 · 풀기 · 다시 올림 유지 · 물음 예일 때만 · 따로 바꾼 줄 보존 · 별도계상 품목 + 단가 0)

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

145 lines
6.3 KiB
TypeScript

/* =============================================================================
* line_stack_material.ts
* 줄 쌓기 가져온 재료 줄 판정 표지 · 같은 파일 물음(PLAN 827-3 · 요구 811-2 · 812-1)
* 표지 = 문서 머리 `가져온.재료`(서버 `M02_San_MatPick.entry`) — 아직 못 이은 줄 「규격 없음 — 고르기」 ·
* 「후보 여럿」 · 「우리 표에 없음」 / 그 파일 고름 줄 「파일 고름」 / 별도계상 품목 · 풍선말 = 판정 · 방법 · 단서 · 후보
* 물음 = 이름 단추로 고른 뒤 한 번 — 「같은 파일의 같은 재료 n 줄에도」 예 = 개인 층 그 파일 고름 +
* 그 파일 다른 단가산출의 가져온 그대로인 줄(서버 `…/material-picks/{지문}`) · 이 줄 = 화면 [저장] · 별칭 아님
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { showToast } from "@ui/ui_template_elements";
import { askConfirm } from "@ui/ui_template_modal";
import type { FormulaDoc } from "../common_util/common_util_formula_doc";
import type { Picked } from "../common_util/common_util_line_stack_pick";
import { call, type Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
import { at } from "../M02_MasterTemplete/M02_MasterTemplete_Hopyo_Api";
import type { MachineCtx } from "./line_stack_machine";
interface Cand {
키: string;
이름?: string;
규격?: string;
단위?: string;
}
export interface MatEntry {
기호: string;
종: string;
원래: { 이름: string; 규격: string; 단위: string };
판정: string;
방법?: string;
키?: string | null;
후보?: Cand[];
단서?: string[];
별도계상?: boolean;
고름?: Cand;
}
interface Head {
재료?: MatEntry[];
지문?: string;
파일?: string;
}
const LINKED = "이어짐";
/** 같은 파일 고름을 저장했음(문서 이벤트) — 불러오기 판이 다시 읽음 */
export const MAT_PICKED = "aislo:mat-picked";
const enc = encodeURIComponent;
const headOf = (d: FormulaDoc): Head | undefined => (d as { 가져온?: Head }).가져온;
const text = (c: { 이름?: string; 규격?: string }): string =>
`${c.이름 ?? ""} ${c.규격 ?? ""}`.trim();
/** 그 단가 기호 줄의 재료 판정 칸 */
export const matEntry = (d: FormulaDoc, sym: string | undefined): MatEntry | undefined =>
sym ? headOf(d)?.재료?.find((x) => x.기호 === sym) : undefined;
/** 풍선말 글 — 판정 · 방법 · 단서 · 후보(앞 다섯) */
export function matTip(e: MatEntry): string {
const cands = (e.후보 ?? []).slice(0, 5).map((c, i) => `${i ? "" : "1 순위 "}${text(c)}`);
return [
`가져온 「${text(e.원래)}」 — ${e.판정}${e.방법 ? ` (${e.방법})` : ""}`,
...(e.고름 ? [`파일 고름: ${text(e.고름)}`] : []),
...(e.단서 ?? []).map((x) => `단서: ${x}`),
...(cands.length ? [`후보: ${cands.join(" · ")}`] : []),
].join("\n");
}
/** 줄 표지 — 못 이은 줄 판정 · 파일 고름 · 별도계상 품목(이어진 줄 = 없음) */
export function materialMark(
e: MatEntry | undefined,
link: { 갈래?: string; 키?: string },
): HTMLElement | null {
if (!e) return null;
const filePick = !!e.고름 && link.키 === e.고름.키;
const label = e.별도계상
? `별도계상 · ${e.고름 ? `품목 ${text(e.고름)}` : e.키 ? "품목 이음" : e.판정}`
: filePick
? "파일 고름"
: e.판정 !== LINKED && link.갈래 === "숫자"
? e.판정
: "";
if (!label) return null;
const done = filePick || (e.별도계상 && !!(e.고름 || e.키));
const badge = createBadge(label, done ? "info" : e.판정 === "우리 표에 없음" ? "neutral" : "warning", { size: 18 }); // prettier-ignore
badge.title = matTip(e);
badge.dataset.matMark = e.판정;
return badge;
}
/** 같은 파일 물음 몸 — 층 · 이 SK · 파일 지문 · 종 · 가져온 글 · 고른 것 */
export interface SameFile {
scope: { layer: Layer; owner: string | null };
me: string;
지문: string;
파일: string;
종: string;
원래: string;
p: Picked;
}
/** 이름 단추로 고른 뒤 — 같은 파일 같은 재료에도(물음 하나 · 예일 때만 서버) */
export async function askFileWide(
ctx: MachineCtx | undefined,
d: FormulaDoc,
sym: string | undefined,
p: Picked,
): Promise<void> {
const e = matEntry(d, sym);
const head = headOf(d);
if (!e || e.별도계상 || !head?.지문 || !ctx?.scope || !ctx.me || p.키 === e.키) return;
await askSameFile({ scope: ctx.scope, me: ctx.me, 지문: head.지문, 파일: head.파일 ?? "", 종: e.종, 원래: text(e.원래), p }); // prettier-ignore
}
/** 같은 파일 같은 재료 물음 — 예 = 개인 층 그 파일 고름 + 그 파일 다른 SK(가져온 그대로인 줄) · 이 SK = 화면 [저장] */
export async function askSameFile(a: SameFile): Promise<void> {
const path = `/material-picks/${enc(a.지문)}`;
const got = await call<{ 수: { 블록: number; 줄: number } | null; 만든: string[] }>(
at(a.scope, `${path}?종=${enc(a.종)}&me=${enc(a.me)}`),
).catch(() => null);
if (!got) return;
const many = got.수 ? `${got.수.줄} 줄(${got.수.블록} 블록)` : "줄";
const made = got.만든.length ? ` · 이미 만든 단가산출 ${got.만든.length} 건의 가져온 그대로인 줄도 바뀜` : ""; // prettier-ignore
const ok = await askConfirm({
message: `같은 파일(${a.파일})의 같은 재료 「${a.원래}」 ${many}에도 「${text(a.p)}」 적용할까? — 다음에 그 파일을 올리면 고른 것이 보임${made} · 별칭 아님`,
okLabel: "같은 파일에 적용",
cancelLabel: "이 줄만",
});
if (!ok) return;
try {
const out = await call<{ 고침: string[] }>(at(a.scope, path), {
method: "POST",
body: JSON.stringify({
종: a.종,
고름: { 키: a.p.키, 이름: a.p.이름, 규격: a.p.규격 ?? "", 단위: a.p.단위 ?? "" },
파일: a.파일,
빼기: [a.me],
}),
});
document.dispatchEvent(new CustomEvent(MAT_PICKED)); // 불러오기 「고를 재료」 판 다시 읽기
showToast(`같은 파일 고름 저장 · 단가산출 ${out.고침.length} 건 고침 — 이 줄은 [저장] 때`, "success"); // prettier-ignore
} catch (err) {
showToast(err instanceof Error ? err.message : String(err), "error");
}
}