Files
Aislo/common_util/common_util_summary_item.ts
T
eomsangdonandClaude Opus 5.5 425a4a9dfb feat(규격 열 고르기): summary_item spec · 사본이 고른 열 굳힘 · 같은 항목 = 열까지 · B07 한 장 = 항목 + 열 · 쓰는 열 지움 409 (PLAN 704-4)
- summary_item spec(파이썬 · TS 짝) — 없으면 기본 열 · 모르는 키 거절 그대로
- 사본 = 고른 열 수량을 손으로 굳힘 · manifest 규격 · same_item 에 spec(열 바꿈 = 새 사본 · 덮개 열쇠 S:폴더 그대로)
- B07 구조물 도면 키 = 항목 + 열 해시 — 열이 다르면 따로 장
- M02 일위대가 [저장] — 쓰는 사본 있는 열 지움 · 이름 바꿈 409
- 항목 목록 labor.규격(열 · 열마다 합계) — 고르기 창 열 단추
- 시험 test_704_4_spec 7(열 바꿈 뒤 덮개 남음 · 짝 잃음 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:16:06 +09:00

93 lines
4.0 KiB
TypeScript

/* =============================================================================
* common_util_summary_item.ts
* 구조물 집계표 항목 참조(PLAN 21-1) — 구조물 · 관 지점이 「어느 집계표 항목」인지 가리키는 값.
*
* summary_item: { layer: "system"|"company"|"personal", owner: null|"{회사}"|"{회사}/{사용자}",
* key: "SS000004", name: "돌기슭막이 메쌓기 1.2m", spec?: "2.0" }
*
* spec(PLAN 704-4) = 그 항목 일위대가 규격 열 이름 — 없으면 기본 열(서버 `parse_summary_item` 과 짝).
*
* 없으면 null(= 미지정 · 집계표에 안 들어감). 옵션(options) 밖의 칸이다.
* import 없음 — 시험이 이 파일만 옮겨 돌림.
* ========================================================================== */
export type SummaryLayer = "system" | "company" | "personal";
export interface SummaryItem {
layer: SummaryLayer;
owner: string | null;
key: string;
name: string;
/** 규격 열 이름(704-4) — 없으면 기본 열 */
spec?: string;
}
const LAYERS: readonly string[] = ["system", "company", "personal"];
/** 참조 모양이 맞는가 — 서버 응답 · 세션 초안을 읽을 때 걸러 쓴다. */
export function isSummaryItem(value: unknown): value is SummaryItem {
if (!value || typeof value !== "object") return false;
const item = value as Record<string, unknown>;
return (
typeof item.layer === "string" &&
LAYERS.includes(item.layer) &&
(item.owner === null || typeof item.owner === "string") &&
typeof item.key === "string" &&
item.key !== "" &&
typeof item.name === "string" &&
(item.spec === undefined || typeof item.spec === "string")
);
}
/** 서버 · 세션에서 온 값을 참조 또는 null 로 — 모양이 틀리면 null(미지정). */
export function readSummaryItem(value: unknown): SummaryItem | null {
return isSummaryItem(value) ? value : null;
}
/** 옵션 그룹(M02 PLAN 46)마다 고른 항목 `{그룹 id: 참조}` — 본체는 `summary_item`. */
export type GroupItems = Record<string, SummaryItem>;
/** 그룹 복사본 id 잇개 — `{구조물 id}__{그룹 id}`(서버 `GROUP_JOIN` 과 짝). */
export const GROUP_JOIN = "__";
/** 서버 · 세션 값을 그룹 참조 묶음으로 — 모양이 틀린 항목은 뺌 · 없으면 빈 묶음. */
export function readGroupItems(value: unknown): GroupItems {
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
return Object.fromEntries(
Object.entries(value as Record<string, unknown>).filter(
(entry): entry is [string, SummaryItem] =>
/^g[0-9a-z]{1,16}$/.test(entry[0]) && isSummaryItem(entry[1]),
),
);
}
/** 저장 · 이음에 실을 연결 칸 — 지정한 것만(본체 `summary_item` · 그룹 `group_items`). */
export function linkFields(source: { summary_item?: unknown; group_items?: unknown }): {
summary_item?: SummaryItem;
group_items?: GroupItems;
} {
const summary = readSummaryItem(source.summary_item);
const groups = readGroupItems(source.group_items);
return {
...(summary ? { summary_item: summary } : {}),
...(Object.keys(groups).length ? { group_items: groups } : {}),
};
}
/** 연결이 하나라도 있나 — 본체 또는 그룹. */
export function hasLinks(source: { summary_item?: unknown; group_items?: unknown }): boolean {
return Object.keys(linkFields(source)).length > 0;
}
/** 표시 글 — 이름(없으면 「미지정」). 키는 앞에 내세우지 않는다(PLAN 21-0). */
export function summaryLabel(item: SummaryItem | null): string {
return item ? item.name || item.key : "미지정";
}
/** 툴팁 — 이름 + 어느 그룹 것인지 · 키는 작은 글로만. */
export function summaryTooltip(item: SummaryItem | null): string {
if (!item) return "집계 항목 미지정 — 집계표에 안 들어감";
const group = { system: "마스터", company: "회사용", personal: "개인용" }[item.layer];
return `${item.name} · ${group} · ${item.key}`;
}