Files
Aislo/A00_Common/line_stack_material.ts
T
eomsangdonandClaude Opus 5.5 dd9ebad8d1 feat(830-10): 불러오기 재료 찾기를 지역 · 상하차 칸에 맞춤 — 가져온 줄 글은 빌더 규칙으로 판독
- 새 M02_Mat_Place: 가져온 이름 · 규격 → (뺀 이름, 뺀 규격, 지역, 상하차)(material_merge 판독 한 벌) · 고르는 차례 rank
- 고르는 차례 = 상하차 맞음(글 없으면 -) › 값 있는 줄 › 줄 지역 › 전국 · 지역 모름 › 서울 › 경기 › 그 밖(값 가운데에 가까운 줄)
- Elem_Alias(_same · one · region_of) · Hopyo_Match(목록에 지역 · 상하차 · 값 · 이름 같음 = 뺀 이름 + 지역 · 상하차 같음) · Hopyo_Material(_places · 변형 지역 = 지역 칸 · _prefer) · MatSame._pick · MatFind 후보 글 = 지역 칸
- 다시 고르기 · 찾기 창 · 줄 풍선말 후보 글에 지역 · 상하차(M01 placeNote 한 벌)
- 시험: 814_1 · m02_hopyo_match 기대를 지역 칸 꼴로 · 새 지킴 test_830_10_material_region 21
- 불러오기 지킴 812_1 · 814_1 · 815_1 · 817_1 · 827_2 · 827_3 · 828_1 · 828_2 · 778_5 ×2 · 793_2 ×2 · 797_1 ×2 · m02_hopyo_match 통과 · typecheck 0

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

148 lines
6.5 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 { placeNote } from "../M01_MasterData/M01_MasterData_Api_Fetch";
import type { MachineCtx } from "./line_stack_machine";
interface Cand {
키: string;
이름?: string;
규격?: 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; 상하차?: string }): string =>
[`${c.이름 ?? ""} ${c.규격 ?? ""}`.trim(), placeNote(c)].filter(Boolean).join(" · ");
/** 그 단가 기호 줄의 재료 판정 칸 */
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");
}
}