From a961e9cb693918964e94ebcd8770c63cb023e3c8 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 14:43:23 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat(716-1=C2=B7716-2):=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=AC=BC=20=EC=98=B5=EC=85=98=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EC=9E=85=EA=B5=AC=20M01=20=C2=B7=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=ED=91=9C=20=EB=91=98=20=C2=B7=20M01=20open=20=EC=9D=B8?= =?UTF-8?q?=EC=9E=90=20(=EC=A4=91=EA=B0=84=20=E2=80=94=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84=EC=84=A0=20=EB=B6=80=ED=92=88=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20=EC=A0=95=EB=A6=AC=20=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M01 왼쪽 단가산출 로직 칸 아래 구조물 옵션 정의 칸 · 본 칸 셋째 모드(저장 안 한 고침 물음) - M02 옛 open=옵션 주소 = 시스템 관리자면 M01 로 돌려보냄 - settingLinks = 대시보드 칸별 줄(group · ?open=) · pageLinks = ☰ 페이지 둘 - 새 글 키 덧만: B01_Dashboard_CardLogic · CardStructOptionDef · CardPrecombo Co-Authored-By: Claude Sonnet 5.5 --- A00_Common/m_settings_nav.ts | 57 +++++++++++++----- .../B01_Dashboard_UI_View_SysDefaults.ts | 16 ++--- M01_MasterData/M01_MasterData_UI_Page.ts | 59 +++++++++++++++++-- M01_MasterData/M01_MasterData_UI_Side.ts | 48 +++++++++++++-- .../M02_MasterTemplete_Layer.ts | 8 +++ .../M02_MasterTemplete_UI_Page.ts | 5 +- .../StructOptions/M02_SO_Style.css | 8 +++ ui_template/ui_template_locale_m1.ts | 2 + ui_template/ui_template_locale_m2.ts | 1 + 9 files changed, 166 insertions(+), 38 deletions(-) diff --git a/A00_Common/m_settings_nav.ts b/A00_Common/m_settings_nav.ts index 73b09f8c7..817dcafc0 100644 --- a/A00_Common/m_settings_nav.ts +++ b/A00_Common/m_settings_nav.ts @@ -1,7 +1,7 @@ /* ============================================================================= * m_settings_nav.ts - * 시스템 설정 바로가기(334 · 요구 225-2) — 대시보드 「시스템 설정」 카드 · M01 · M02 ☰ 가 같이 쓰는 - * 역할 규칙 한 벌(`settingLinks`) · ☰ 진행 단계 자리에 🏠 대시보드 + 카드(`mountSettingsDrawer`). + * 시스템 설정 바로가기(334 · 요구 225-2 · 716-2) — 대시보드 설계 기본값 줄(`settingLinks` · 칸마다 한 줄 · + * `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`). * ========================================================================== */ import { ROUTES, type RoutePath } from "@config/config_frontend"; @@ -16,52 +16,63 @@ interface SettingUser { company_id?: number | null; } -/** key = M01 은 「m01」 · 계산 자리 창은 「calcSites」(대시보드 카드만 · 504-2) · M02 는 진입 인자 `open` */ +/** key = 페이지 열쇠(☰ · 「m01」 「m02」) 또는 칸 열쇠(대시보드 줄 · 계산 자리 「calcSites」 = 창 504-2) · + * group = 대시보드 설계 기본값 묶음 제목 */ export interface SettingLink { key: string; title: string; desc: string; route: RoutePath; query?: Record; + group?: string; } function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } -const ICONS: Record = { m01: "🗄️", 구조물조합: "🧱", 옵션: "🎛️", 도면: "📑" }; +const ICONS: Record = { m01: "🗄️", m02: "🧱" }; -/** 시스템 설정 — 역할로 카드 숨김 · M02 카드는 `?open=&root=` 로 진입(27-1d) */ +/** 설계 기본값 줄(대시보드) — 칸마다 한 줄 · 역할로 숨김 · M01 줄 `?open=`(칸 고름) · M02 줄 `?open=&root=`(27-1d). + * 칸을 더할 때는 아래 표에 한 줄(m01 / m02 호출)만 더함. */ export function settingLinks(user: SettingUser, companyId: number | string | null): SettingLink[] { // 시스템 관리자는 고른 회사를 해시에 실음(29-1) const company: Record = user.role === "SYSTEM_ADMIN" && companyId ? { company: String(companyId) } : {}; + const m01 = (key: string, title: string, desc: string, open?: string): SettingLink => ({ + key, + title, + desc, + route: ROUTES.M01_MASTER_DATA, + query: open ? { open } : undefined, + group: "마스터 데이터", + }); const m02 = (title: string, open: string, root: string, desc: string): SettingLink => ({ key: open, title, desc, route: ROUTES.M02_MASTER_TEMPLATE, query: { open, root, ...company }, + group: "마스터 템플릿", }); // 구조물 조합 기본 층 = 개인(123-1 · 누구든) · 개인 층 없음(회사 없음) = 아래 역할 규칙 const comboRoot = (fallback: string): string => (user.company_id ? "personal" : fallback); if (user.role === "SYSTEM_ADMIN") { const desc = L("B01_Dashboard_PermMasterWrite"); + const read = L("B01_Dashboard_PermSourceRead"); return [ - { - key: "m01", - title: L("B01_Dashboard_MasterElements"), - desc: L("B01_Dashboard_PermSourceRead"), - route: ROUTES.M01_MASTER_DATA, - }, + m01("m01", L("M01_Title"), read), + m01("로직", L("B01_Dashboard_CardLogic"), read, "로직"), + m01("옵션", L("B01_Dashboard_CardStructOptionDef"), desc, "옵션"), + m02(L("B01_Dashboard_CardPrecombo"), "일위대가조합", comboRoot("master"), desc), m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot("master"), desc), - m02(L("B01_Dashboard_CardStructOption"), "옵션", "master", desc), m02(L("B01_Dashboard_CardDrawing"), "도면", "master", desc), { key: "calcSites", title: L("B01_CalcSites_Title"), desc: L("B01_CalcSites_Desc"), route: ROUTES.B01_ACCOUNT, + group: "계산", }, ]; } @@ -76,14 +87,30 @@ export function settingLinks(user: SettingUser, companyId: number | string | nul return [m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot(root), desc)]; } -/** ☰ 진행 단계 자리 — 🏠 대시보드 + 카드 · 지금 칸 강조 · 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */ +/** ☰ 메뉴 — 페이지 둘만(M01 = 시스템 관리자 · M02 = 누구나) · 열쇠 「m01」 「m02」 */ +export function pageLinks(user: SettingUser, companyId: number | string | null): SettingLink[] { + const combo = settingLinks(user, companyId).find((link) => link.key === "구조물조합"); + const links: SettingLink[] = []; + if (user.role === "SYSTEM_ADMIN") + links.push({ + key: "m01", + title: L("B01_Dashboard_MasterElements"), + desc: "", + route: ROUTES.M01_MASTER_DATA, + }); + if (combo) + links.push({ ...combo, key: "m02", title: L("M02_Title"), desc: "", group: undefined }); + return links; +} + +/** ☰ 진행 단계 자리 — 🏠 대시보드 + 페이지 둘 · 지금 페이지 강조(`activeKey` = 「m01」 「m02」) · + * 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */ export function mountSettingsDrawer( user: SettingUser, activeKey: string, companyId: number | string | null, ): void { - // 계산 자리 = 대시보드 창(504-2) — 페이지가 아니라 ☰ 에 안 둠 - const links = settingLinks(user, companyId).filter((link) => link.key !== "calcSites"); + const links = pageLinks(user, companyId); const bar = createStepBar( links.map((link) => link.title), links.findIndex((link) => link.key === activeKey), diff --git a/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts b/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts index df9a8a70e..72060c3df 100644 --- a/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts +++ b/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts @@ -1,7 +1,8 @@ /* ============================================================================= * B01_Dashboard_UI_View_SysDefaults.ts - * 시스템 관리 › 설계 기본값(549-2) — 마스터 데이터 · 구조물 조합 · 구조물 옵션 · 도면 양식 · 계산 자리. - * 카드 규칙은 `settingLinks` 한 벌(334) — 여기는 모양만(묶음 제목 + 둥근 상자 안 줄) · 계산 자리 = 창(504-2). + * 시스템 관리 › 설계 기본값(549-2 · 716-2) — 마스터 데이터(마스터 요소 · 단가산출 로직 · 구조물 옵션 정의) · + * 마스터 템플릿(일위대가 · 구조물 조합 · 도면 양식) · 계산 자리. 칸마다 한 줄(누르면 그 칸을 고른 채 열림 · `?open=`). + * 줄 · 묶음 규칙은 `settingLinks` 한 벌(334) — 여기는 모양만(묶음 제목 + 둥근 상자 안 줄) · 계산 자리 = 창(504-2). * ========================================================================== */ import { createButton } from "@ui/ui_template_elements"; @@ -12,22 +13,13 @@ import { pageFrame, settingCard, settingRow } from "./B01_Dashboard_UI_PageKit"; import { group } from "./B01_Dashboard_UI_View_Parts"; import type { ViewCtx } from "./B01_Dashboard_UI_Views"; -/** 카드 열쇠 → 묶음 제목 — 모르는 열쇠는 「그 밖」 */ -const GROUP_OF: Record = { - m01: "마스터 데이터", - 구조물조합: "구조물 · 도면", - 옵션: "구조물 · 도면", - 도면: "구조물 · 도면", - calcSites: "계산", -}; - const open = (link: SettingLink): void => link.key === "calcSites" ? void openCalcSitesDialog() : navigateTo(link.route, link.query); export function mountSysDefaults(host: HTMLElement, ctx: ViewCtx): void { const groups = new Map(); for (const link of settingLinks(ctx.me, ctx.companyId)) { - const title = GROUP_OF[link.key] ?? "그 밖"; + const title = link.group ?? "그 밖"; const openBtn = createButton({ label: "열기", variant: "ghost", onClick: () => open(link) }); groups.set(title, [...(groups.get(title) ?? []), settingRow(link.title, link.desc, openBtn)]); } diff --git a/M01_MasterData/M01_MasterData_UI_Page.ts b/M01_MasterData/M01_MasterData_UI_Page.ts index c40020fd3..f4a2ed43b 100644 --- a/M01_MasterData/M01_MasterData_UI_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Page.ts @@ -1,8 +1,8 @@ /* ============================================================================= * M01_MasterData_UI_Page.ts * 마스터 요소 화면 — 좌측 도킹 패널(컨테이너: 인력·재료·기계·소요량·계수·환율·요율·단가산출 로직) - * · 일위대가 조합은 M02 마스터 템플릿(PLAN 27-2b) - * / 우측 요소 표 · 표 목록 · 로직 편집 + * · 일위대가 조합은 M02 마스터 템플릿(PLAN 27-2b) · 구조물 옵션 정의(716-1 · 화면은 M02 `StructOptions/`) + * / 우측 요소 표 · 표 목록 · 로직 편집 · 옵션 정의 · 주소 `?open=로직|옵션` = 그 칸을 고른 채 열림(716-2) * * 시스템 관리자만. 고친 것은 초안(sessionStorage)에 쌓이고 [저장] 한 번에 서버로 — * 자동저장 없음. @@ -26,7 +26,7 @@ import { t as L } from "@ui/ui_template_locale"; import { createWorkflowOverlays } from "@ui/ui_template_overlay"; import { ROUTES } from "@config/config_frontend"; import { mountSettingsDrawer } from "../A00_Common/m_settings_nav"; -import { navigateTo } from "../A00_Common/router"; +import { currentQuery, navigateTo, setLeaveGuard } from "../A00_Common/router"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { saveFiles, type FileInfo } from "./M01_MasterData_Api_Fetch"; import { discard, isStale, onDraftChange, payload, totalCount } from "./M01_MasterData_Draft"; @@ -89,6 +89,7 @@ function buildPage(): HTMLElement { children: [headBar.root, buildHeadFilters([searchField.root])], }); const logicHost = el("div", { className: "m01-master__logic", attrs: { hidden: "" } }); + const optionsHost = el("div", { className: "m02-sod__host", attrs: { hidden: "" } }); const elementView = el("div", { className: "m01-master__elements", children: [ @@ -112,12 +113,50 @@ function buildPage(): HTMLElement { onDraftChange(refreshBar); refreshBar(); - const showMode = (mode: "elements" | "logic"): void => { + const showMode = (mode: "elements" | "logic" | "options"): void => { logicHost.hidden = mode !== "logic"; elementView.hidden = mode !== "elements"; + optionsHost.hidden = mode !== "options"; + if (mode !== "options") side.setOptionsMark(false); + }; + + /* --- 구조물 옵션 정의(716-1) — 저장 안 한 고침은 칸을 바꿀 때 · 페이지를 떠날 때 물음 --- */ + let optionsDirty = false; + const leaveOptions = async (): Promise => { + if (!optionsDirty || optionsHost.hidden) return true; + if (!(await showConfirmDialog(L("M02_DiscardConfirm")))) return false; + optionsDirty = false; + return true; + }; + /** 옵션 정의에 고침이 있으면 물은 뒤에 `run` — 취소면 옵션 줄 표시를 되돌려 머묾 */ + const afterLeave = (run: () => void): boolean => { + if (!optionsDirty || optionsHost.hidden) return true; + void leaveOptions().then((ok) => { + if (ok) return run(); + side.setActive(null); + side.setOptionsMark(true); + }); + return false; + }; + const showOptions = (label: string, node: HTMLElement): ((dirty: boolean) => void) => { + dispose(); + dispose = (): void => {}; + current = null; + optionsDirty = false; + optionsHost.replaceChildren( + el("div", { + className: "m01-master__headbox", + children: [buildHeadBar({ title: label, buttons: [] }).root], + }), + node, + ); + showMode("options"); + setLeaveGuard(leaveOptions); + return (dirty) => (optionsDirty = dirty); }; const openFile = (pick: Pick): void => { + if (!afterLeave(() => openFile(pick))) return; dispose(); showMode("elements"); if (pick.view) { @@ -203,6 +242,7 @@ function buildPage(): HTMLElement { /** 「단가산출 로직」 탭으로 전환 — `key` 가 있으면 그 로직을 바로 엶. */ const openLogicTab = (key?: string): void => { if (key) setPendingLogicKey(key); + if (!afterLeave(() => openLogicTab())) return; dispose(); dispose = (): void => {}; current = null; @@ -259,7 +299,11 @@ function buildPage(): HTMLElement { setMasterJump((target) => void jump(target)); /* --- 좌측: 컨테이너 --- */ - const side = buildSide(openFile, () => openLogicTab()); + const side = buildSide(openFile, () => openLogicTab(), { + confirmLeave: leaveOptions, + show: showOptions, + onOpen: () => side.setActive(null), + }); const layout = el("div", { className: "ui-workflow-layout m01-master" }); const main = el("main", { @@ -267,7 +311,7 @@ function buildPage(): HTMLElement { children: [ el("div", { className: "m01-master__panel", - children: [elementView, logicHost], + children: [elementView, logicHost, optionsHost], }), ], }); @@ -287,5 +331,8 @@ function buildPage(): HTMLElement { ); // 다른 화면(일위대가 조합 다이어그램 등)이 로직 키를 들고 왔으면 로직 탭으로 바로 엶 if (hasPendingLogicKey()) openLogicTab(); + // 주소 `?open=` — 대시보드 설계 기본값 줄 · 옛 M02 `open=옵션` 즐겨찾기가 그 칸을 고른 채 엶 + const want = currentQuery().get("open"); + if (want === "로직" || want === "옵션") side.reveal(want); return layout; } diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index 0f9e752f0..1c7e90b0e 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -1,13 +1,18 @@ /* ============================================================================= * M01_MasterData_UI_Side.ts - * 왼쪽 패널 — 공용 접기 컨테이너(`ui-collapsible ui-sidebar-section`) 아홉 개 + * 왼쪽 패널 — 공용 접기 컨테이너(`ui-collapsible ui-sidebar-section`) * 인력 · 재료 · 기계 · 소요량 · 계수 · 환율 · 요율 · 단가산출 로직(일위대가 조합은 M02 · PLAN 27-2b) + * ─ 구분선 ─ 구조물 옵션 정의(716-1 · 화면 코드는 M02 `StructOptions/` 그대로 · 입구만 여기) * 펼치면 하위 목록(거름 · 파일) · 환율/요율은 누르면 바로 표 · 로직은 안에 로직 목록 * ========================================================================== */ import { createSelectField, el, showToast } from "@ui/ui_template_elements"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { currentLanguageIndex, t as L } from "@ui/ui_template_locale"; +import { + buildOptionsSection, + type OptionsSectionOptions, +} from "../M02_MasterTemplete/StructOptions/M02_SO_Options"; import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; import { tx } from "./M01_MasterData_UI_Logic_Text"; import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree"; @@ -103,6 +108,10 @@ export interface SideHandle { setOnPicked: (fn: () => void) => void; /** 「전체」 + 드롭다운 거름 한 벌 — 로직 컨테이너도 씀 */ filter: (args: FilterArgs) => HTMLElement; + /** 주소 `open` — 그 컨테이너를 펼치고 고름(로직 = 로직 목록 · 옵션 = 정의 화면까지) */ + reveal: (target: "로직" | "옵션") => void; + /** 옵션 줄 고름 표시 지움(다른 화면을 열었음) · 켬(떠나기를 취소해 머묾) */ + setOptionsMark: (on: boolean) => void; } export interface FilterArgs { @@ -157,7 +166,11 @@ const LEAF: Group[] = ["환율", "요율"]; const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"]; const LOGIC_ID = "로직|"; -export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): SideHandle { +export function buildSide( + onOpen: (pick: Pick) => void, + onLogic: () => void, + optionsOpt: Omit, +): SideHandle { const lists = new Map(); const bodies = new Map(); const buttons = new Map(); @@ -483,9 +496,12 @@ ${first.file}` onLogic(); }); + // 구조물 옵션 정의 — 로직 칸 아래 구분선 + 칸 하나(시스템 관리자 화면이라 고침 가능) + const options = buildOptionsSection({ ...optionsOpt, isAdmin: true }); + const optionsSec = section(options.title, options.body); const root = el("div", { className: "m01-side ui-sidebar-panel", - children: [...groups, logic], + children: [...groups, logic, el("hr", { className: "ui-sidebar-divider" }), optionsSec], }); attachCollapsible(root); void Promise.all(GROUPS.filter((g) => g !== FUEL_GROUP).map(refresh)).catch((error) => @@ -497,5 +513,29 @@ ${first.file}` // 재료는 유가 · 전력 파일까지 다(알약 → 마스터 화면 길이 파일로 찾음) · 유가 · 전력은 그 파일만 const files = (group: string): FileInfo[] => group === FUEL_GROUP ? shown(FUEL_GROUP) : (lists.get(group as Group) ?? []); - return { root, logicHost, refresh, files, setActive, setOnPicked, filter }; + const reveal = (target: "로직" | "옵션"): void => { + if (target === "옵션") { + optionsSec.classList.remove("is-collapsed"); + options.body.querySelector("button")?.click(); + return; + } + logic.classList.remove("is-collapsed"); + setActive(buttons.has(LOGIC_ID) ? LOGIC_ID : null); + onLogic(); + }; + const setOptionsMark = (on: boolean): void => { + if (!on) return options.clear(); + options.body.querySelector("button")?.classList.add("is-active"); + }; + return { + root, + logicHost, + refresh, + files, + setActive, + setOnPicked, + filter, + reveal, + setOptionsMark, + }; } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Layer.ts b/M02_MasterTemplete/M02_MasterTemplete_Layer.ts index ce046f785..242e0fa69 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Layer.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Layer.ts @@ -51,6 +51,14 @@ export function entryFromHash(user?: SessionUser | null): Entry { return { root, owner, open, company: query.get("company") || null, pre }; } +/** 옛 주소 `open=옵션`(구조물 옵션 정의는 M01 로 옮김 · 716-1) — 시스템 관리자면 M01 그 칸으로 돌려보냄 · + * 돌려보냈으면 참(그 밖 역할은 읽기 화면이 없어져 구조물 조합으로 열림) */ +export function redirectOldOptions(user: SessionUser | null): boolean { + if (user?.role !== "SYSTEM_ADMIN" || currentQuery().get("open") !== "옵션") return false; + navigateTo(ROUTES.M01_MASTER_DATA, { open: "옵션" }); + return true; +} + /** 진입 해시 인자 — `navigateTo(ROUTES.M02_MASTER_TEMPLATE, entryQuery(…))` */ export const entryQuery = (entry: Entry): Record => ({ open: entry.open, diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts index 17cb6cbc6..436262468 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts @@ -31,6 +31,7 @@ import { entryQuery, layerOfRoot, ownOwner, + redirectOldOptions, viewCompany, type OpenTarget, type Root, @@ -61,6 +62,8 @@ const NEW_ASK = export async function renderM02MasterTemplate(root: HTMLElement): Promise { const user = await fetchSessionUser().catch(() => null); + // 구조물 옵션 정의는 M01 로 옮김(716-1) — 옛 주소는 M01 그 칸으로 + if (redirectOldOptions(user)) return { covers: () => false, show: () => {}, dispose: () => {} }; const entry = entryFromHash(user); // 134-4 — 해시에 층 없음 = 개인(회사 없으면 마스터) const company = viewCompany(entry, user); // 볼 회사(시스템 관리자는 고른 회사) let layer = layerOfRoot(entry.root); @@ -283,7 +286,7 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise false, show: () => {}, dispose: () => setActiveLayer("system") }; diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Style.css b/M02_MasterTemplete/StructOptions/M02_SO_Style.css index 32fcae514..8815da5fa 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Style.css +++ b/M02_MasterTemplete/StructOptions/M02_SO_Style.css @@ -404,3 +404,11 @@ table.m02-sod__table--wrap { word-break: keep-all; overflow-wrap: anywhere; } + +/* M01 본 칸(716-1) — 정의 화면을 얹는 틀: 머리 줄 아래 화면 */ +.m02-sod__host { + display: flex; + flex-direction: column; + gap: var(--spacing-12); + min-width: 0; +} diff --git a/ui_template/ui_template_locale_m1.ts b/ui_template/ui_template_locale_m1.ts index 04c28d9a3..6c686efd7 100644 --- a/ui_template/ui_template_locale_m1.ts +++ b/ui_template/ui_template_locale_m1.ts @@ -171,4 +171,6 @@ export const ui_locales_m1 = { B01_CalcSites_Saved: ["계산 자리 저장함", "Calc sites saved"], B01_CalcSites_Leave: ["저장 안 한 계산 자리가 있음", "You have unsaved calc sites"], B01_CalcSites_LoadFailed: ["계산 자리를 못 읽음", "Failed to load calc sites"], + B01_Dashboard_CardLogic: ["단가산출 로직", "Cost-logic library"], + B01_Dashboard_CardStructOptionDef: ["구조물 옵션 정의", "Structure option definition"], } as const; diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index 3a5811a1d..0639ba79f 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -188,4 +188,5 @@ export const ui_locales_m2 = { "그 사이 실무문서가 바뀜 — 목록을 다시 받음", "Practice documents changed — reloaded", ], + B01_Dashboard_CardPrecombo: ["일위대가", "Unit-cost items"], } as const; From a2742d528bbaf76b5f71abd5ed79986cece2d51f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 14:46:48 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(716-1=C2=B7716-2):=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=B9=B8=20=EC=9C=84=20=EA=B5=AC=EB=B6=84=EC=84=A0?= =?UTF-8?q?=20=EB=B6=80=ED=92=88=20=C2=B7=20=EC=8B=9C=ED=97=98=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M01 로직 칸 아래 구분선 = 부품 createDivider - 시험 셋 새 꼴로: 대시보드 줄 칸별(M01 3 · M02 3 · 계산 자리) · ☰ 페이지 둘(역할별) · 옛 open=옵션 돌려보냄 · M01 open 인자 - 낡은 시험 고침 까닭: 옵션 줄이 M01 로 옮겨 카드 순서 · 해시가 바뀜(test_b01_settings_cards · test_197_1 · test_309_2) Co-Authored-By: Claude Sonnet 5.5 --- M01_MasterData/M01_MasterData_UI_Side.ts | 3 +- .../test_197_1_dashboard_option_card.py | 62 ++++++++++++--- resources/tester/test_309_2_calc_sites.py | 2 +- resources/tester/test_b01_settings_cards.py | 76 +++++++++++++++---- 4 files changed, 116 insertions(+), 27 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index 1c7e90b0e..5dc8f06a7 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -8,6 +8,7 @@ import { createSelectField, el, showToast } from "@ui/ui_template_elements"; import { attachCollapsible } from "@ui/ui_template_collapsible"; +import { createDivider } from "@ui/ui_template_divider"; import { currentLanguageIndex, t as L } from "@ui/ui_template_locale"; import { buildOptionsSection, @@ -501,7 +502,7 @@ ${first.file}` const optionsSec = section(options.title, options.body); const root = el("div", { className: "m01-side ui-sidebar-panel", - children: [...groups, logic, el("hr", { className: "ui-sidebar-divider" }), optionsSec], + children: [...groups, logic, createDivider(), optionsSec], }); attachCollapsible(root); void Promise.all(GROUPS.filter((g) => g !== FUEL_GROUP).map(refresh)).catch((error) => diff --git a/resources/tester/test_197_1_dashboard_option_card.py b/resources/tester/test_197_1_dashboard_option_card.py index 9bb89ea86..011bbbe1c 100644 --- a/resources/tester/test_197_1_dashboard_option_card.py +++ b/resources/tester/test_197_1_dashboard_option_card.py @@ -1,10 +1,14 @@ -"""197-1 대시보드 「구조물 옵션」 카드 → M02 「구조물 옵션 정의」 화면이 바로 열림 시험 — 조작은 사용자. +"""197-1 · 716-1 대시보드 「구조물 옵션 정의」 줄 → M01 「구조물 옵션 정의」 화면이 바로 열림 시험 — 조작은 ORCA. + +716-1 — 화면 입구가 M02 에서 M01 로 옮겨감(화면 코드 · 서버 길은 M02 폴더 그대로) · 옛 M02 `open=옵션` 주소는 +시스템 관리자면 M01 `?open=옵션` 으로 돌려보냄. 원인 — 진입 해시 `open=옵션` 이 옵션 줄을 눌러 정의 화면을 띄우는 사이 첫 화면(집계표)의 `mount` 가 모듈 · 번호를 기다리다 뒤늦게 끝나 정의 화면 위에 집계표를 덮음(`mount` 에 낡음 검사 없었음). -· 카드 셋 → 해시(open=구조물조합 · 옵션 · 도면) · 해시 → 진입(`entryFromHash`) Node 로 바로 돌림 -· `reveal` 가 옵션만 줄을 누르고 도면은 펼치기만 · 구조물조합은 아무것도 안 함 +· 줄 → 해시(M01 open=로직 · 옵션 / M02 open=일위대가조합 · 구조물조합 · 도면) · 해시 → 진입(`entryFromHash`) Node +· M02 `reveal` 가 옵션만 줄을 누르고 도면은 펼치기만 · 구조물조합은 아무것도 안 함 · M01 `reveal` 도 같은 꼴 +· 옛 `open=옵션` 돌려보냄(`redirectOldOptions`) · `mount` 집계표 갈래(진짜 소스 조각)를 Node 로 돌림 — 기다리는 사이 다른 화면이 열리면 그리지 않음 """ @@ -41,16 +45,19 @@ def _run(script: str, *args: str) -> object: @needs_node -def test_카드_셋_해시(): +def test_줄_해시_옵션은_M01(): from test_b01_settings_cards import _cards cards = _cards({"role": "SYSTEM_ADMIN", "is_master": False}, 3) - assert [c[2].split("?")[1].split("&")[0] for c in cards[1:4]] == [ - "open=구조물조합", - "open=옵션", - "open=도면", + heads = [c[2].split("?")[0] + "?" + c[2].split("?")[1].split("&")[0] for c in cards[1:6]] + assert heads == [ + "m01-master-data?open=로직", + "m01-master-data?open=옵션", + "m02-master-template?open=일위대가조합", + "m02-master-template?open=구조물조합", + "m02-master-template?open=도면", ] - assert all("&root=master" in c[2] for c in cards[1:4]) + assert all("&root=master" in c[2] for c in cards[3:6]) @needs_node @@ -69,7 +76,7 @@ def test_해시_진입_셋(): + fn.group(0).replace("export function", "function") + "\nconsole.log(JSON.stringify(entryFromHash(null)));" ) - for want in ("구조물조합", "옵션", "도면"): + for want in ("구조물조합", "옵션", "도면", "일위대가조합"): got = _run(script, f"open={want}&root=master&company=3") assert got["open"] == want and got["root"] == "master" and got["company"] == "3" @@ -139,3 +146,38 @@ def test_집계표_갈래_기다리는_사이_다른_화면이_열리면_그리 assert got["none"] == ["create", "panel:set"] # 안 끼어들면 그대로 그림 assert got["mod"] == ["panel:null"] # 모듈 기다리는 사이 다른 화면 — 그리지 않음 assert got["num"] == ["panel.destroy", "panel:null"] # 번호 기다리는 사이 — 패널도 버림 + + +@needs_node +def test_옛_옵션_주소_M01로_돌려보냄(): + layer = _text(M02 / "M02_MasterTemplete_Layer.ts") + fn = re.search(r"^export function redirectOldOptions\([\s\S]*?^}", layer, re.M) + assert fn, "redirectOldOptions 를 못 찾음" + script = ( + "const currentQuery = () => new URLSearchParams(process.argv[1]);\n" + "const ROUTES = { M01_MASTER_DATA: 'm01' }; const sent: string[] = [];\n" + "const navigateTo = (r: string, q: any) => sent.push(r + JSON.stringify(q));\n" + "type SessionUser = any;\n" + + fn.group(0).replace("export function", "function") + + "\nconst a = redirectOldOptions({ role: 'SYSTEM_ADMIN' });" + + "\nconst b = redirectOldOptions({ role: 'USER' });" + + "\nconst c = redirectOldOptions(null);" + + "\nconsole.log(JSON.stringify([a, b, c, sent]));" + ) + assert _run(script, "open=옵션&root=master") == [True, False, False, ['m01{"open":"옵션"}']] + # 옵션이 아니면 안 돌려보냄 + assert _run(script, "open=도면&root=master") == [False, False, False, []] + page = _text(M02 / "M02_MasterTemplete_UI_Page.ts") + assert "if (redirectOldOptions(user))" in page + assert 'mountSettingsDrawer(user ?? {}, "m02", entry.company);' in page + + +def test_M01_open_인자_로직_옵션(): + m01 = ROOT / "M01_MasterData" + page = _text(m01 / "M01_MasterData_UI_Page.ts") + assert 'if (want === "로직" || want === "옵션") side.reveal(want);' in page + assert "setLeaveGuard(leaveOptions)" in page # 저장 안 한 정의 고침 — 떠날 때 물음 + side = _text(m01 / "M01_MasterData_UI_Side.ts") + assert "buildOptionsSection({ ...optionsOpt, isAdmin: true })" in side + # 로직 칸 아래 구분선 부품 + 옵션 칸 + assert "logic, createDivider(), optionsSec" in side diff --git a/resources/tester/test_309_2_calc_sites.py b/resources/tester/test_309_2_calc_sites.py index 4a366502d..df6a56949 100644 --- a/resources/tester/test_309_2_calc_sites.py +++ b/resources/tester/test_309_2_calc_sites.py @@ -263,7 +263,7 @@ def test_카드_시스템_관리자만_글_다_있음(): links = nav.split("export function mountSettingsDrawer")[0] admin, rest = links.split('if (user.role === "SYSTEM_ADMIN") {')[1].split("// 회사 수정 =") assert 'key: "calcSites"' in admin and "calcSites" not in rest - assert 'settingLinks(user, companyId).filter((link) => link.key !== "calcSites")' in nav + assert "calcSites" not in nav.split("export function pageLinks")[1] # ☰ 는 페이지 둘만(716-2) page = _text("B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts") assert 'link.key === "calcSites" ? void openCalcSitesDialog()' in page locale = _text("ui_template/ui_template_locale_m1.ts") diff --git a/resources/tester/test_b01_settings_cards.py b/resources/tester/test_b01_settings_cards.py index 8220ed667..a32175595 100644 --- a/resources/tester/test_b01_settings_cards.py +++ b/resources/tester/test_b01_settings_cards.py @@ -58,7 +58,36 @@ def _cards(user: dict, company_id: int | None = None) -> list: return json.loads(result.stdout) +def _pages(user: dict, company_id: int | None = None) -> list: + # ☰ 메뉴 = pageLinks(settingLinks 의 구조물 조합 줄을 바탕으로 페이지 둘만) + script = ( + "const ROUTES = { M01_MASTER_DATA: 'm01-master-data'," + " M02_MASTER_TEMPLATE: 'm02-master-template', B01_ACCOUNT: 'b01-account' };\n" + "const L = (k: string) => k;\n" + "let went = '';\n" + "const navigateTo = (r: string, q?: Record) =>" + " { went = r + (q ? '?' + new URLSearchParams(q) : ''); };\n" + + _fn(_text(NAV), "settingLinks") + + "\n" + + _fn(_text(NAV), "pageLinks") + + "\nconst user = JSON.parse(process.argv[1]);" + + "\nconst links = pageLinks(user, JSON.parse(process.argv[2]));" + + "\nconsole.log(JSON.stringify(links.map((l: any) => {" + + " navigateTo(l.route, l.query); return [l.key, l.title, decodeURIComponent(went)]; })));" + ) + result = subprocess.run( + ["node", "--experimental-strip-types", "--no-warnings", + "--input-type=module-typescript", "-e", script, json.dumps(user), json.dumps(company_id)], + capture_output=True, text=True, encoding="utf-8", timeout=60, + ) # fmt: skip + assert result.returncode == 0, result.stderr + return json.loads(result.stdout) + + +M01 = "m01-master-data" M02 = "m02-master-template" +READ_SRC = "B01_Dashboard_PermSourceRead" +WRITE_M = "B01_Dashboard_PermMasterWrite" # 계산 자리(504-2) — 시스템 관리자만 · 이동 대신 창 CALC_SITES = ["B01_CalcSites_Title", "B01_CalcSites_Desc", "calc-sites-dialog"] READ_CO = ( @@ -68,21 +97,40 @@ WRITE_CO = READ_CO.replace("CompanyRead", "CompanyWrite") @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") -def test_시스템_관리자_카드_다섯_고른_회사_해시(): - m = "B01_Dashboard_PermMasterWrite" +def test_시스템_관리자_줄_칸별_일곱_고른_회사_해시(): + # 716-2 — 대시보드 설계 기본값 = 칸별 줄: M01 3(마스터 요소 · 단가산출 로직 · 구조물 옵션 정의) + + # M02 3(일위대가 · 구조물 조합 · 도면 양식) + 계산 자리 assert _cards({"role": "SYSTEM_ADMIN", "is_master": False}, 3) == [ - ["B01_Dashboard_MasterElements", "B01_Dashboard_PermSourceRead", "m01-master-data"], - ["B01_Dashboard_CardStructCombo", m, f"{M02}?open=구조물조합&root=master&company=3"], - ["B01_Dashboard_CardStructOption", m, f"{M02}?open=옵션&root=master&company=3"], - ["B01_Dashboard_CardDrawing", m, f"{M02}?open=도면&root=master&company=3"], + ["M01_Title", READ_SRC, M01], + ["B01_Dashboard_CardLogic", READ_SRC, f"{M01}?open=로직"], + ["B01_Dashboard_CardStructOptionDef", WRITE_M, f"{M01}?open=옵션"], + ["B01_Dashboard_CardPrecombo", WRITE_M, f"{M02}?open=일위대가조합&root=master&company=3"], + ["B01_Dashboard_CardStructCombo", WRITE_M, f"{M02}?open=구조물조합&root=master&company=3"], + ["B01_Dashboard_CardDrawing", WRITE_M, f"{M02}?open=도면&root=master&company=3"], CALC_SITES, ] # 회사가 없으면 해시에 안 실음 - assert _cards({"role": "SYSTEM_ADMIN", "is_master": False})[1][2] == ( + assert _cards({"role": "SYSTEM_ADMIN", "is_master": False})[4][2] == ( f"{M02}?open=구조물조합&root=master" ) +@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") +def test_메뉴_페이지_둘만_역할별(): + # 716-2 — ☰ = M01 · M02 두 페이지(시스템 관리자) / M02 한 페이지(그 밖) · 열쇠 m01 · m02 + admin = _pages({"role": "SYSTEM_ADMIN", "is_master": False, "company_id": 1}, 3) + assert admin == [ + ["m01", "B01_Dashboard_MasterElements", M01], + ["m02", "M02_Title", f"{M02}?open=구조물조합&root=personal&company=3"], + ] + assert _pages({"role": "ADMIN", "is_master": False, "company_id": None}) == [ + ["m02", "M02_Title", f"{M02}?open=구조물조합&root=company"], + ] + assert _pages({"role": "USER", "is_master": False, "company_id": 1}) == [ + ["m02", "M02_Title", f"{M02}?open=구조물조합&root=personal"], + ] + + @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_회사_관리자_개인_카드_하나(): company = [ @@ -191,14 +239,12 @@ def test_회사_찾기_거르기(): def test_구조물_조합_기본_층_개인(): """123-1 — 회사 있는 사람은 역할 무관 구조물 조합 = 개인 · 회사 없으면 지금 규칙 · 다른 곳 그대로 · 134-4 — 층 없는 주소도 개인(회사 없으면 마스터) · 사전조합 기본 층도 같은 규칙.""" - m = "B01_Dashboard_PermMasterWrite" combo = f"{M02}?open=구조물조합&root=personal" - assert _cards({"role": "SYSTEM_ADMIN", "is_master": False, "company_id": 1}, 3) == [ - ["B01_Dashboard_MasterElements", "B01_Dashboard_PermSourceRead", "m01-master-data"], - ["B01_Dashboard_CardStructCombo", m, f"{combo}&company=3"], - ["B01_Dashboard_CardStructOption", m, f"{M02}?open=옵션&root=master&company=3"], - ["B01_Dashboard_CardDrawing", m, f"{M02}?open=도면&root=master&company=3"], - CALC_SITES, + rows = _cards({"role": "SYSTEM_ADMIN", "is_master": False, "company_id": 1}, 3) + assert rows[3:6] == [ + ["B01_Dashboard_CardPrecombo", WRITE_M, f"{M02}?open=일위대가조합&root=personal&company=3"], + ["B01_Dashboard_CardStructCombo", WRITE_M, f"{combo}&company=3"], + ["B01_Dashboard_CardDrawing", WRITE_M, f"{M02}?open=도면&root=master&company=3"], ] assert _cards({"role": "ADMIN", "is_master": False, "company_id": 1}) == [ ["B01_Dashboard_CardStructCombo", WRITE_CO, combo], @@ -206,7 +252,7 @@ def test_구조물_조합_기본_층_개인(): assert _cards({"role": "USER", "is_master": True, "company_id": 1})[0][2] == combo assert _cards({"role": "USER", "is_master": False, "company_id": 1})[0][2] == combo # 개인 층 없음(회사 없음) = 지금 규칙 - assert _cards({"role": "SYSTEM_ADMIN", "is_master": False, "company_id": None})[1][2] == ( + assert _cards({"role": "SYSTEM_ADMIN", "is_master": False, "company_id": None})[4][2] == ( f"{M02}?open=구조물조합&root=master" ) assert _cards({"role": "ADMIN", "is_master": False, "company_id": None})[0][2] == ( From dd5e00f0ba2ea4d553e16b90242a6d5ac7f047a1 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 14:48:02 +0900 Subject: [PATCH 3/5] =?UTF-8?q?knowledge(=EC=9A=94=EA=B5=AC):=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=2014-6=20EST=20=EC=9E=A5=EB=B9=84=20=EC=A4=84=20?= =?UTF-8?q?=EC=A1=B0=EC=82=AC=20=E2=80=94=20=EC=9E=A5=EB=B9=84=20=ED=95=9C?= =?UTF-8?q?=20=EC=A4=84=20+=20=EB=85=B8=20=C2=B7=20=EC=9E=AC=20=C2=B7=20?= =?UTF-8?q?=EA=B2=BD=20=EC=B9=B8=20=C2=B7=20=EC=9A=94=EA=B5=AC=EC=84=9C=20?= =?UTF-8?q?=EC=A0=95=ED=95=A8=206=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../요구_내역서/_분석_내역14-6_EST_장비줄.md | 186 ++++++++++++++++++ .../요구서_내역12_단가산출로직_M02단가산출.md | 4 +- 2 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 docs/raw/요구_내역서/_분석_내역14-6_EST_장비줄.md diff --git a/docs/raw/요구_내역서/_분석_내역14-6_EST_장비줄.md b/docs/raw/요구_내역서/_분석_내역14-6_EST_장비줄.md new file mode 100644 index 000000000..efbd71e89 --- /dev/null +++ b/docs/raw/요구_내역서/_분석_내역14-6_EST_장비줄.md @@ -0,0 +1,186 @@ +# 내역 14-6 — 장비 줄(기사 · 기름 · 손료) 보이는 꼴 · EST · STmate 조사 + +읽기 전용 조사 · 결정 아님. 줄임: +- `실무/` = `resources/knowledge/original/실무문서/` · `EST/` = `resources/knowledge/original/경쟁사 프로그램/EST Plus 2/REA 분석/` +- 노 · 재 · 경 = 노무비 · 재료비 · 경비 + +## ① 결론 다섯 줄 + +1. EST 는 장비를 **흩지 않음** — 장비 하나 = 한 줄(일위대가) 또는 한 덩어리(단가산출 「가. 굴착기」)이고, 그 안 돈만 노 · 재 · 경 **칸**에 나눠 적음. 기사가 인력 줄로, 기름이 재료 줄로 따로 나오는 꼴(나)은 표본 어디에도 없음. +2. 장비 속(손료 · 주연료 · 잡품 · 운전사)은 산근 아래가 아니라 **따로 붙은 표 「기계경비」(산출표) + 「기계경비총괄표」** 에 나옴 — 손료 → 경비 칸 · 주연료 · 잡품 → 재료비 칸 · 운전사 → 노무비 칸. 산근 · 일위대가는 총괄표의 시간당 노 · 재 · 경 세 값을 끌어다 씀. +3. STmate 도 똑같음 — 「중기사용료」(산출표) · 「중기목록표」(총괄표) · 산근 글 `노무비 : 55,700 / Q` · 일위대가 한 줄 노 · 재 · 경 칸. 집계표도 인력 집계에 운전사 없음 · 「중기 시간,금액 집계표」 따로. +4. 원가계산서로는 칸 그대로 감 — 운전사 → 직접노무비 · 연료 · 잡품 → 직접재료비 · 손료 → 산출경비(EST 거창 수식 `직접노무비 = 내역서!J85`(노무비 열)). 우리 지금 셈(M01 엔진 · B09)도 이미 같음 → 바꿀 것은 화면 묶음 보기뿐. +5. 권함 = **(가)** — 장비 묶음에 한 줄 + 그 줄 노 · 재 · 경 칸 + 펼치면 시간당 손료 · 주연료 · 잡품 · 운전사 넉 줄(EST 기계경비표 한 덩어리와 같은 줄). 실무자가 EST · STmate · 손 엑셀에서 매일 보는 꼴이고, 품셈 원문 줄(「굴착기 0.1 hr」)과 1:1 로 맞음. + +## ② EST 표본 + +### 2-0 이 파일들이 EST 출력인 근거 + +- EST 엑셀 내보내기 창 `TFRMEXCELCONVERT` 의 고르기 칸 = 노임단가 · 자재단가 · 견적단가 · 표준단가 · **기계경비 적용기준 · 기계경비 총괄표 · 기계경비** · 단가산출 총괄표 · 단가산출 · 일위대가 총괄표 · 일위대가 · 내역서 총괄표 · 내역서 · 원가계산(`EST/02_화면구성/captions.json:17947-17996`). +- 아래 다섯 파일의 시트 차례가 이것과 같음: 원가계산서 · 내역서총괄표 · 내역서 · (일위대가총괄표 · 일위대가) · 단가산출총괄표 · 단가산출 · 기계경비총괄표 · 기계경비 · 기계경비적용기준 · 자재단가 · 노임단가. + - A `실무/2026_실무일위대가 모음/2026년_북부지방산림청_임도사업_설계내역서(공통단가).xls` + - B `실무/01.산불거창(거창북상병곡)/01.내역서/00.25년_산불_거창(…)내역+수량.xlsx` (수식 살아 있음) + - C `실무/2026_실무일위대가 모음/2026년_북부지방산림청_사방사업(계류+댐)_설계내역서.xls` + - D `실무/2026_실무일위대가 모음/2026_상반_산림청_수목진료.xls` + - (E 2016 사방품셈(안) xls 도 같은 꼴 · 장수 · 진안 xlsx 는 DRM 잠김(`DRMONE` 머리) — 못 읽음) + +### 2-1 「기계경비」 시트 = 장비 속 산출표 (손료 · 연료 · 잡품 · 운전사) + +머리 칸: B 명칭 · C 규격 · D 산출근거 · Z 합계 · **AA 노무비 · AB 재료비 · AC 경비** · AD 비고. + +A `기계경비!4-8`: + +| 줄 | B | 산출근거 | AA 노무비 | AB 재료비 | AC 경비 | +|---|---|---|---|---|---| +| 4 | 1. 덤프트럭 4.5Ton (머리) | | 49,778 | 9,494 | 7,613 | +| 5 | 손 료 | 25,662,000 ₩× 2,967 × 10(-7) | | | 7,613.9 | +| 6 | 주연료 경유 | 5 ℓ × 1,376 | | 6,880 | | +| 7 | 잡 품 주연료의 | 38 % × 6,880 | | 2,614.4 | | +| 8 | 화물차운전사 | 1 인 × 238,936 × 1/8*16/12*25/20 | 49,778.3 | | | + +B `기계경비!4-8`(수식): 머리 `AA4 = TRUNC(AA5+AA6+AA7+AA8)` · 손료 `AC5 = TRUNC(D5*K5*0.0000001,1)` · 주연료 `AB6 = TRUNC(D6*J6,1)`(`J6 = 자재단가!T5`) · 잡품 `AB7 = TRUNC(D7/100*J7,1)` · 운전사 `AA8 = TRUNC(D8*I8*기계경비적용기준!E21,1)`(`I8 = 노임단가!H4`). + +D `기계경비!4-7` 동력분무기 4.85kw = 손료 258(경비) · 휘발유 1,914.9 · 잡품 382.9(재료) · **운전사 줄 없음 → 노무비 0**. 같은 시트 9-13 물탱크 = 화물차운전사 49,778.3(노무). + +### 2-2 「기계경비총괄표」 = 장비마다 한 줄 · 시간당 노 · 재 · 경 + +머리 칸: No · 품명 · 규격 · 단위 · 합계 · **노무비 · 재료비 · 경비**. + +B `기계경비총괄표!21`: `18 | 굴삭기(타이어형) | 0.6㎥ | hr | F=G21+H21+I21 | G=기계경비!AA111 | H=기계경비!AB111 | I=기계경비!AC111` → 값 105,932 = 59,020 + 19,792 + 27,120. + +A `기계경비총괄표!6`: `3 | 굴착기(무한궤도) | 0.7㎥ | hr | 103,047`(노 59,020 · 재 19,473 · 경 24,554 — xls 저장값은 G~I 칸이 0 으로 읽히고 같은 값이 당초 칸 AA~AC 에 있음 · 수식 미계산 저장으로 보임). + +### 2-3 「단가산출」(산근) 안 장비 = 한 덩어리 + 노 · 재 · 경 세 글 줄 + 소계 + +열: 산출근거(글) · 합계 · 노무비 · 재료비 · 경비. + +A `단가산출!18-42` (뒷채움돌 채집): + +``` +18 가. 굴착기(타이어형)(0.6㎥) +20 q = 0.6㎥ K = 0.55 f = 1 / 22 E = 0.35 Cm = 18sec(90˚) +25 Q = … = 23.1 ㎥/hr +28 노무비 : 59,020 ÷ 23.1 × 90% = 2,299.4 +29 재료비 : 19,792 ÷ 23.1 × 90% = 771.1 +30 경 비 : 27,120 ÷ 23.1 × 90% = 1,056.6 +31 소 계 : 4,127.1 | 합 4,127.1 | 노 2,299.4 | 재 771.1 | 경 1,056.6 +33 나. 인 력 (10%) +35 보통인부 : 172,068 × 0.3인/㎥ × 10% = 5,162 | 합 5,162 | 노 5,162 +37 다. 소 계 +39 노무비 : 2,299.4 + 5,162 = 7,461.4 +40 재료비 : 771.1 +41 경 비 : 1,056.6 +42 계 : 9,289.1 | 합 9,289.1 | 노 7,461.4 | 재 771.1 | 경 1,056.6 +``` + +B `단가산출!4-33` (측구터파기 토사 · 노 · 재 · 경 = EM · EN · EO 열): + +``` + 7 1.기계(100%):굴삭기(무한궤도)(0.7㎥) +14 Q = … = 23.39㎥/hr +17 노무비:59,020÷23.39=2,523.3 +18 재료비:19,473÷23.39=832.5 +19 경비:24,554÷23.39=1,049.7 +20 소계:4,405.5 | EL 4405.5 | EM 2,523.3 | EN 832.5 | EO 1,049.7 +23 2.보조인부(1일당1인) +25 보통인부:172,068÷8.÷23.39=919.5 | EM 919.5 +28 3.합계 +30 노무비:2,523.3+919.5=3,442 +33 계:5,323 | EL 5323 | EM 3,442 | EN 832 | EO 1,049 +``` + +같은 시트 40-58(측구터파기 연암): 「가. 굴삭기(0.7+브레이카)」 노 11,804 · 재 3,703 · 경 7,193.2 + 「나. 치즐(0.7㎥용) 267.6 → 재료비」 + 「다. 소계」. + +C `단가산출!40-62` (가설창고 운반): 「가. 크레인(타이어)(10Ton) Q = 2hr」 → `노무비 : 59,020 × 2 = 118,040` · `재료비 : 7,268 × 2 = 14,536` · `경 비 : 31,344 × 2 = 62,688` · 소계 195,264 → 「나. 인건비」 비계공 · 특별인부 240,406 → 「다. 소계」 노무비 118,040 + 240,406 = 358,446. + +- 숫자 칸은 「소계」 줄에만 노 · 재 · 경 셋이 함께 찍힘. 노무비 · 재료비 · 경비 글 줄은 글뿐. +- 장비 노무(기사)는 덩어리 안에 머물다가 「다. 소계 · 합계」에서야 인력 노무와 더해짐. + +### 2-4 「일위대가」 안 장비 = 한 줄 · 노 · 재 · 경 단가 칸 셋 + +열: 품명 · 규격 · 수량 · 단위 · 합계(단가 · 금액) · 노무비(단가 · 금액) · 재료비(단가 · 금액) · 경비(단가 · 금액). + +B `일위대가!10`: `굴삭기(타이어형) | 0.6㎥ | 0.1 | hr | 105,932 · 10,593.2 | 59,020 · 5,902 | 19,792 · 1,979.2 | 27,120 · 2,712` — 수식 `I10 = 기계경비총괄표!G21` · `K10 = …!H21` · `M10 = …!I21`. + +C `일위대가!104`: `콘크리트 펌프차 | 41m,80~95㎥/hr | 9.38 | hr | 195,494 | 노 59,020 | 재 43,282 | 경 93,192`. + +### 2-5 EST 화면 · 엔진 + +- 단가산출 편집기 표 = 코드 · 구분 · **산출근거(글)** · 합계 · 노무비 · 재료비 · 경비 · 실적. 우클릭 「노무비 · 재료비 · 경비 · 소계 지정 · 노,재,경 해제(Shift+F5~F9)」 — 글 줄마다 어느 칸으로 갈지 표시(`docs/raw/요구_내역서/_분석_내역3-A_EST_화면.md:65`). 작업도구 「기계경비 공식」 = 위 「노무비 : … ÷ Q」 꼴 글을 끼워 넣는 틀(`captions.json:21139-21251`). +- 산근 글의 `59,020` 은 손으로 친 수가 아니라 **기계 목록의 노무(NC) 칸 연결**(LinkIndex 105) · 재료비 줄 = GC · 경비 줄 = JC(`EST/09_…/ESTPlus2_산근LineData_수량연결_정적분석_20261029.md:25,42`). +- 기계 상세 셈: 손료 → JC(경비) · 연료 · 잡품 → GC(재료) · 운전사 → NC(노무) · 머리 합 원 미만 절사(`EST/09_…/ESTPlus2_기계경비_정적분석_20261029.md`). +- 일위대가 우클릭 「비목별 단가 불러오기 ▸ 노무비만 · 재료비만 · 경비만 · 전체비목」(`_분석_내역3-A_EST_화면.md:56`) — 장비 줄에서 일부 비목만 쓰는 길(기본 = 전체). +- 운전사 노임을 다른 비목으로 옮기는 설정은 화면 글 전체에 없음(`captions.json` 「운전 · 조종」 검색 0). + +## ③ STmate 표본 (짧게) + +봉화 `실무/04.산불 봉화/01. 내역서/2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx` + +| 시트 · 줄 | 줄 그대로 | +|---|---| +| 중기사용료 5-11 | 제 1 호표 굴삭기(무한궤도) 0.2㎥ 시간 → `굴삭기(무한궤도) 0.2㎥ 0.2085 천원 62,550 → 경비 13,041.6` · `건설기계운전사 1 인 55,700 → 노무 55,700` · `경유 저유황 5 L 1,273 → 재료 6,365` · `잡품 주연료의% 21 % → 재료 1,336.6` · `합 계 76,442 | 노 55,700 | 재 7,701 | 경 13,041` | +| 중기목록표 4 | `1 | 굴삭기(무한궤도) | 0.2㎥ | 시간 | 76,442 | 노 55,700 | 재 7,701 | 경 13,041` | +| 단가산출근거 69-78 | `노 무 비 : 55,700 / Q = 3,545.5`(노 칸) · `재 료 비 : 7,701 / Q = 490.1`(재 칸) · `경 비 : 13,041 / Q = 830.1`(경 칸) · `소계 4,865.7` · `계` — 숨은 식 `'노 무 비 :'&X00001L& / Q` · `X00001M` · `X00001S` = 중기 X00001 의 노(L) · 재(M) · 경(S) | +| 일위대가표 9 | `굴삭기(타이어) | 0.6㎥,(부착용집게) | 0.3 | 시간 | 105,178 | 노 55,700 | 재 18,310 | 경 31,168` | +| 노무비수량금액집계표 4-19 | 건축목공 · 형틀목공 · 석공 · 보통인부 … — **운전사 줄 없음** | +| 재료비수량금액집계표 | 경유 · 휘발유 줄 없음(단가표 「재료비목록표」에만 단가로 있음) | +| 중기시간금액집계표 4-12 | 장비마다 한 줄 · 수량(시간) · 합계 · 노 · 재 · 경 — 예 `굴삭기(무한궤도) 0.7㎥ | 940.69 시간 | 91,099,241 | 노 52,396,433 | 재 16,946,530 | 경 21,756,278` | + +영월 `일위대가표!27`: `크레인(타이어) | 10톤 | 2 | 시간 | 92,526 | 노 55,700 | 재 6,723 | 경 30,103 | X02104`. + +- 다른 점 하나: STmate 산근은 노무비 · 재료비 · 경비 글 줄마다 자기 칸에 숫자를 찍음(EST 는 소계 줄에만). 꼴은 같음. +- STmate 규칙 문서도 「연료 · 소모품 = 재료 · 운전원 = 노무 · 손료 = 경비로 나눠 상위 단가에 전달」(`…/STmate 분석/20_분석/07_규칙2_3_4_5_직접비_단가_중기_자재.md:51`). + +## ④ 원가계산서로 가는 비목 + +| 출처 | 운전사(기사) | 주연료 · 잡품 | 손료 | +|---|---|---|---| +| 건설 표준품셈 8-1-6 | 기계경비 = 기계손료 + 운전경비(연료 · 운전 노무비 · 소모품) + 수송비 — **어느 비목인지는 안 적음**(`…/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계/8-01_적용기준.md:165-172`) | 〃 | 〃 | +| 예정가격작성기준 제19조 3호 | 「기계경비 = 표준품셈상 건설기계 경비산정기준에 의한 비용」(경비 세비목) — 안의 노무 · 연료를 따로 보내라는 글 없음(`…/행정규칙/(계약예규) 예정가격작성기준/현행_20260825.md:180`) | 〃 | 〃 | +| EST | 직접노무비 | 직접재료비 | 산출경비 | +| STmate | 직접노무비 | 직접재료비 | 산출경비 | +| 우리 지금 셈 | 직접노무비 | 직접재료비 | 산출경비 | + +- EST 근거 = 거창 `원가계산서!F4 = 내역서!L85`(재료비 열) · `F8 = 내역서!J85`(노무비 열) · `F11 = 내역서!N85`(경비 열) — 내역서 열 = 일위대가 · 산근 노 · 재 · 경 칸 합이고, 장비 노무가 그 칸에 들어 있음(②-3 · ②-4). +- STmate 근거 = 봉화 직접노무비 426,802,539 ↔ 인력 집계만 146,900,061 · 중기 노무 집계 294,516,923 → 중기 노무를 넣어야 맞는 크기(남는 3 % 차이는 덧줄 · 끝수 · 집계 범위로 보임 · 미대조). +- 우리 = M01 엔진 로직 줄 = 하위 노 · 재 · 경을 수량만큼 나눠 얹음(`resources/master_data/scripts/master_formula.py:588-599`) · B09 중기사용료 손료 경비 · 운전사 노무 · 연료 · 잡품 재료(`B09_Estimation/B09_Estimation_Calc_Base.py:239-293`) · B09 산근 텍스트 중기 = 노 · 재 · 경 세 줄(`B09_Estimation/B09_Estimation_Sangeun.py:27-28`) · 원가계산서 직접노무비 = 내역서 노무 합(`B09_Estimation/B09_Estimation_Calc_Total.py:9`). +- 뜻: 운전사 몫이 직접노무비에 들어가므로 간접노무비 · 산재 · 고용 · 건강 · 연금 · 퇴직공제 요율이 운전사 몫에도 걸림. 장비 돈을 「경비」 한 칸으로 몰면 원가계산서 금액이 달라짐 → **금액 칸은 꼭 노 · 재 · 경으로 나눈 채** 둬야 함. (가) · (나) 어느 쪽이든 칸만 나뉘면 원가계산서 결과는 같음 — 다른 것은 보기뿐. + +## ⑤ 우리 권함 · 화면 그림 + +권함 = (가) 장비 묶음 한 줄 + 노 · 재 · 경 칸 + 펼침. + +까닭: +1. 실무 출력 셋(EST · STmate · 손 엑셀 거창 숨은 「중기경비집계표」까지)이 모두 이 꼴 — 장비 한 줄 · 노 · 재 · 경 칸 나뉨 · 속은 따로 붙은 장비 산출표. +2. 품셈 원문 줄과 1:1 — 원문은 「굴착기 0.1 hr」 한 줄. (나)로 흩으면 「건설기계운전사 0.0043 인 · 경유 0.5 ℓ」처럼 원문에 없는 줄이 생겨 원문 대조 · 검수가 어긋남. +3. 인력 묶음에 기사가 섞이지 않음 — STmate 인력 집계에도 운전사 없음. 실무자 눈에 「인력 = 사람 품」. +4. 지금 셈을 안 고침 — 엔진 · B09 가 이미 노 · 재 · 경을 나눠 들고 있음. 묶음 파생만 더함(`_분석_내역12-B_로직원론.md:135` 규칙 「로직 줄 · 대상 원/시간 = 장비 묶음」). + +화면 그림(단가산출 상세 · 묶음 보기): + +``` +묶음 / 명칭 규격 수량 단위 | 합계 노무비 재료비 경비 +▾ 인력 + 보통인부 0.03 인 | 5,162 5,162 + 인력 소계 | 5,162 5,162 +▾ 재료 + (없음) +▾ 장비 + 굴착기(타이어) 0.6㎥ 0.039 hr | 4,127.1 2,299.4 771.1 1,056.6 ← 한 줄 + ▸ 펼침(시간당 · 기계경비 산출 · 거창 `기계경비!111-115` 값) + 손료 119,000,000 × 2,279 × 10⁻⁷ | 27,120.1 + 주연료 경유 11.6 ℓ × 1,376 | 15,961.6 + 잡품 주연료의 24 % × 15,961.6 | 3,830.7 + 운전사 건설기계운전사 1 인 × 283,297 × 1/8·16/12·25/20 | 59,020.2 + 시간당 계 | 105,932 59,020 19,792 27,120 + 장비 소계 | 4,127.1 2,299.4 771.1 1,056.6 +────────────────────────────────────────────────────────────────── +계 | 9,289.1 7,461.4 771.1 1,056.6 +``` + +- 장비 줄 노무비 칸은 「장비 소계」에 머물고 맨 아래 「계」에서 인력 노무와 더해짐(EST 「다. 소계 노무비 2,299.4 + 5,162」와 같음). +- 펼침 = EST 「기계경비」 시트 한 덩어리 · STmate 「중기사용료」 한 호표와 같은 넉 줄. +- 운전사 없는 장비(동력분무기 · 굴삭기 부착 콤팩터 · 덤프 자동덮개)는 노무비 칸 빈칸 · 펼침에 운전사 줄 없음(표본 D). +- 묶음 이름(「장비」 · 「기계경비」 · 「중기」)은 사용자 협의 — EST 툴바 단추 = 「장비」 · 표 이름 = 「기계경비」 · STmate = 「중기」. + +곁가지(이번 물음 밖 · 확인만): 거창 일위대가 `9` 「공구손료 및 경장비(진동기)의 기계경비 | 노무비의 2 %」가 **재료비 칸**(K)에 들어감 — 우리 로직 덧줄은 비목 칸 따라(대부분 경비 · `_분석_내역12-B_로직원론.md:132`). 갈림 여부는 따로 볼 일. diff --git a/docs/raw/요구_내역서/요구서_내역12_단가산출로직_M02단가산출.md b/docs/raw/요구_내역서/요구서_내역12_단가산출로직_M02단가산출.md index de6b7298a..1f0278360 100644 --- a/docs/raw/요구_내역서/요구서_내역12_단가산출로직_M02단가산출.md +++ b/docs/raw/요구_내역서/요구서_내역12_단가산출로직_M02단가산출.md @@ -57,7 +57,7 @@ 3. 보기 = 넷(단가산출 상세 · 인력·재료·경비 · 시험 계산 · 텍스트 수식) · 기본정보 늘 맨 위 접힘 · 상세 머리에 「지금 입력」 한 줄 · 보기 기억 = 화면 안에서만. 4. 숫자 고침 자리 = M01 단가산출 상세는 보기 전용(정본 불변) · M02 단가산출 · 일위대가 모달은 단가산출 상세 표의 품 · 단가 칸을 바로 고침(고친 칸 색 + M01 값 곁). 5. 인력·재료·경비 보기 = 704-2 계산서 보기를 공용 「단가산출 계산서」 부품으로 올려 M01 · M02 · 일위대가 모달 · 기준 목록 미리 보기가 한 벌로 씀 · 줄 꼴 = 산출근거 글(「석공 : 단가 × 품 = 금액」) + 그룹 머리 변수 칸 · 변수 가르기 = 서버가 줄마다 「쓰는 입력」 줌. -6. 기계경비 · 복합 하위 로직 줄 꼴 = 4-1 물음(권함 = 한 줄 + 노 · 재 · 경 금액 칸 + 펼침). +6. 장비(기계경비) 줄 = 경비(장비) 묶음에 한 줄 + 그 줄 안 노 · 재 · 경 금액 칸 · 펼치면 손료 · 주연료 · 잡품 · 운전사 넉 줄(EST · STmate 같은 꼴 · `_분석_내역14-6_EST_장비줄.md`) · 장비 노무는 장비 소계에 머물다 맨 아래 「계」에서 인력 노무와 더함 · 원가계산서 비목 배정은 지금 셈 그대로 · 복합 하위 로직 = 한 줄 + 펼침 · 묶음 이름 = 「장비」. 7. 갈라진 로직 = 4-1 물음(권함 = 정본 그대로 · 목록 묶음 접기) · 작업량 로직 27 = 부르는 로직 밑으로 접음 · 비고 「별도」 = 계산 밖 「관련 로직」 링크. 8. 기준단위 = 원문 배 그대로 · 표 `기준` → 「배 · 단위」 두 칸은 읽을 때 파생(정본 표 안 씀) · 화면에 환산 근거. 9. 단위 변환 = 별칭 · 개수 묶음 · 배 읽기 한 벌 + 부피 ↔ 무게 = 마스터 단위중량 표 CC000337 고르기(기본 줄 = 사용자 확인) · 밀도 · ㎾h 는 경고만. @@ -83,7 +83,7 @@ 1. 「일위대가 사전조합」 새 이름 = 「일위대가」 · 「실무문서」 = 도면 양식 뒤 그대로(정함). 2. 갈린 로직 = 원문을 보고 판단 · 누가 바꾸거나 더한 로직일 수 있음 → 조사 중(`_분석_내역14-5_로직_원문대조.md`) · 결과 뒤 정함 7 고침. -3. 장비 줄 꼴 = EST 가 한 방식 확인 → 조사 중(`_분석_내역14-6_EST_장비줄.md`) · 결과 뒤 정함 6 고침. +3. 장비 줄 꼴 = EST 가 한 방식 확인 → EST · STmate 모두 장비 한 줄 + 노 · 재 · 경 칸 · 흩어 넣기 없음 → 정함 6. 4. M02 단가산출 고침 범위 = 숫자 · 줄 더하기/빼기(식 안 받음) · 옵션 열어 둔 단가산출의 배율 규칙은 정함 13 그대로. 5. 일위대가 안 단가산출 창에서 고친 값 = 그 일위대가에만 + [단가산출로 저장]. 6. 별칭 모음 = 시스템 관리자가 승인한 것만 모두에게 · 공사 내역 맞춤도 같이 모음(정함). From 9387f2642a3716255dd1155ed81ed0ce37564176 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 15:02:07 +0900 Subject: [PATCH 4/5] =?UTF-8?q?knowledge(=EC=9A=94=EA=B5=AC):=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=2015=20=EC=9B=90=EB=AC=B8=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=C2=B7=2014-5=20=EC=9B=90=EB=AC=B8=20=EB=8C=80=EC=A1=B0=20?= =?UTF-8?q?=E2=80=94=20=EC=A1=B0=EC=A2=85=EC=9B=90=20=ED=99=98=EC=82=B0=20?= =?UTF-8?q?=EB=B9=BC=EA=B3=A0=20=EB=B3=B8=EB=AC=B8=EB=8C=80=EB=A1=9C=20?= =?UTF-8?q?=C2=B7=20=EC=AA=BC=EA=B0=A0=2012=20=ED=95=A9=EC=B9=A8=20=C2=B7?= =?UTF-8?q?=20=ED=84=B0=ED=8C=8C=EA=B8=B0=20=ED=95=9C=20=EA=BC=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../_분석_내역14-5_로직_원문대조.md | 158 ++++++++++++++++++ docs/raw/요구_내역서/사용자요구_내역서.md | 6 + 2 files changed, 164 insertions(+) create mode 100644 docs/raw/요구_내역서/_분석_내역14-5_로직_원문대조.md diff --git a/docs/raw/요구_내역서/_분석_내역14-5_로직_원문대조.md b/docs/raw/요구_내역서/_분석_내역14-5_로직_원문대조.md new file mode 100644 index 000000000..e1f98e8d7 --- /dev/null +++ b/docs/raw/요구_내역서/_분석_내역14-5_로직_원문대조.md @@ -0,0 +1,158 @@ +# 내역 14-5 — M01 단가산출 로직 원문 대조 · 고친 이력 (조사 에이전트 · 11-01) + +읽기 전용 조사. 코드 · 자료 안 고침. 수치 = git 이력 · `resources/master_data/로직_*.json` · 원문 md · storage 를 일회 스크립트로 직접 센 값(스크립트 지움). +경로 줄임 — `MD/` = `resources/master_data/` · `원문/` = `resources/knowledge/original/원가계산/` · `M01/` = `M01_MasterData/M01_MasterData_`. + +## ① 결론 다섯 줄 + +1. **사람 · 화면이 정본 로직을 고치거나 더한 흔적 없음.** 로직 파일을 바꾼 커밋(09-19 작성 69 · 09-20 뒤 74)은 모두 AI 작업임. 화면 편집은 자체 로직 GX 3 개(10-01)뿐이고, 다음 날 사용자 지시로 지움. **다만 고치는 길은 지금도 열려 있음** — 시스템 관리자가 M01 화면 [저장] · 흐름도 [저장]을 누르면 정본 로직 · 표 줄이 바뀜(고침 · 지움 · 더함). 이력 없이 덮어씀. +2. **로직 1,469 개 모두 원문 절에 닿음 — 원문에 없는 절을 가리키는 로직 0.** + - 1,354 개 = 09-19 오후 AI 창들이 원문 md 를 장마다 읽고 직접 씀(로직을 만드는 빌더 스크립트는 없음). + - 115 개 = 09-30 원문 대조에서 「절 누락」으로 나온 원문 소절을 채움. 원문에 없는 것을 덧붙인 것이 아님. +3. **갈라진 로직은 거의 다 원문 짜임 그대로임.** 로직이 여럿인 절 122 곳(355 로직) = 원문 소절 · 표 · 항목 · 열을 따라 갈린 곳 97 · 단가 + 작업량 짝(엔진 꼴) 22 · 그 밖 3(13-1-1 · 13-3-1 임의 · 공통 8장 규정 갈래). + - 구조물터파기 24 = 원문 소절 18 에 단가 로직 18 이 하나씩 짝 + 작업량 로직 6. + - 택지조성측량 6 도 원문 표가 여섯임. 앞 조사 12-B 의 「한 표 → 합칠 후보」 판정을 바로잡음. +4. **임의로 쪼갠 것은 12 로직뿐이고, 이것을 쓰는 M02 문서 · 프로젝트는 없음.** + - 13-1-1 플랜트 배관 5 = 원문 한 표를 인쇄 쪽이 나뉜 대로 쪼갬. + - 13-3-1 기초다짐 3 = 원문 한 표. + - 작업량 복제 4 = 원문 「0~1m와 동일」을 로직 넷으로 베낌. + - 엔진 쓰임새로 나눈 작업량 27 · 기계경비 10 은 까닭이 있으니 그대로 둠. +5. **원문과 다른 것 — 사람이 바꾼 것은 0, 품셈 본문 밖 근거가 하나.** + - 기계경비 로직 8 의 조종원 시간 환산(1/8 · 16/12 · 25/20)은 품셈 본문이 아니라 대한건설협회 질의회신(2001)에서 옴. 이 8 개를 부르는 로직 471 에 번짐. + - 그 밖 「해석:」 728 · 주 미반영 13 · 결정목록 물음 127(흠 365 줄)은 원문이 덜 정한 곳을 AI 가 메운 것임. 사용자 답을 기다리는 중. + +## ② 이전 기능 이력 — 로직을 고치거나 더하던 길 + +| 언제 | 기능 | 정본 로직에 닿나 | 지금 | 근거 | +|---|---|---|---|---| +| 09-07~09-18 | 옛 공종 마스터(축 · B08/B09) — 품셈 476 표 정규화 · 사람이 가른 표 형태 66 · B09 프로젝트 일위대가 · 산근 구성행 · Q 식 고치기 · STmate 엑셀 → 내 라이브러리 | 옛 체계(다른 파일) | 09-19 마스터 재구축 때 `MD/old/` 로 옮김 · 지금 로직은 이어받지 않고 새로 씀 | `3497486b` · `c3618fe8` · `d6fd3c17` · `0e6412fe` | +| 09-19 | M01 관리자 화면 호표 편집기(수량 식 · 중간 · 덧줄 · 끝수 칸 고쳐 [저장]) | **정본 줄을 바로 고침**(`/save`) | 09-21 사용자 「정본은 못 고침」 확정 → 편집기는 자체 로직에만 씀 · 길(`/save`)은 남음 | `3a36a037` · `10598f36` · `docs/raw/M01_쉬운단가/쉬운단가_검토_D_지난시도.md` 1번 | +| 09-20~09-21 | 방식 A·B·C · 흐름 그림 · 새로 만들기 마법사 · 본떠 만들기 · 식으로 새로 만들기 | 자체 로직 GX(`MD/로직_자체.json`)만 | 마법사 · A·B·C 는 하루 안에 걷음 · 본떠 · 식 만들기 서버 길은 남음 · 단추는 10-01 지움 | `c57f0ea0` · `be8868ca` · `946425af` · `5b693242` | +| 10-01 | 시험판 셋(쉬운 단가산출 · 로직 만들기 판 · 시트 로직) | 자체만 | 10-01 지움 | `67ced35a` | +| 10-01~10-02 | 흐름도 — 관리자가 정본 로직을 흐름도로 고쳐 [저장] → 그 줄은 흐름이 정본 · 옛 식 칸(입력 · 중간 · 호표 · 덧줄) 지움 · 새 로직 = GX | **정본 줄을 고침** | 지금도 있음(관리자만 편집 · `M01/UI_LogicLab.ts:302` `readOnly: !admin`) · 정본 줄 「흐름」 칸 = 0(아무도 저장 안 함) | `7b90d28e` · `28da700d` · `docs/raw/M01_쉬운단가/흐름도_적용_정함.md` | +| 10-01 | 사용자 화면 편집 — GX000001 메쌓기(인력) · GX000002 채움재 · GX000003 메쌓기 | 자체만 | 10-02 사용자 지시로 지움(사본 없음) | `57ce7bb2` · `89f8af2a` · `66d0fd58` | +| 10-05~10-09 | M02 단가승격(SP) · 사전조합(SC) 서고 | 안 닿음(M02 층) | 10-09 승격 서고 지움 · 이름 「일위대가화」 | `9ee6c9f3` · `15f2e7c4` · `8ad85167` | + +지금 정본에 쓰는 길: + +| 길 | 하는 일 | 막는 장치 | +|---|---|---| +| `POST /api/m01/save` — M01 화면 [저장](로직 · 표 · 요소 모두) | 정본 장 파일 줄 고침 · 지움 · 더함(새 GF/GC 키 발급) · 통째로 덮어씀 | 시스템 관리자만 · 판 다르면 409 · 검사 오류 422 · **이력 · 개정 표시 없음** | +| 흐름도 [저장](같은 `/save`) | 정본 줄에 「흐름」 칸 · 옛 식 칸 지움 | 같음 | +| `/logic/edit` · `/logic/delete` · `/logic/new` · `/logic/copy` · `/logic/formula/save` · `/logic/draft` | 자체 파일(GX)만 | 정본은 403 | + +근거 — `M01/Store.py:626-695`(`_apply` · `save`) · `M01/Store_Flow.py:378-406` · `M01/Router.py:266-330`. + +## ③ 이력 — 누가 무엇을 바꿨나 + +첫 작성 = 09-19 15:32~19:55. AI 창들이 원문 md 를 장마다 읽고 손으로 씀(커밋 글 「건설품셈 공통 3장(토공사) 로직 50줄」 꼴). 로직 빌더는 없음(`MD/_갱신절차.md`). 그 뒤 키 단위 바뀜(09-20 키 개편부터 · 합치기 아닌 커밋): + +| 갈래 | 언제 | 커밋 | 로직 | 성격 | +|---|---|---|---|---| +| 첫 작성 | 09-19 | 69(장 작성 · 장 사이 고침 · 노임 · 재료 연결) | 1,354 | 원문 분석 · AI | +| 키 개편 | 09-20 | 1(`6c9ac97b` · `MD/scripts/_migrate_keys.py`) | 1,351 | 열쇠 → 키 · 뜻 그대로 | +| 꼴 · 단가 연결 | 09-20~10-05 | 26 | 1,354(겹침) | 구분 칸 · 원문번호 꼴 · 옛 직종 이름 · 자재품목 · 재료 고르기 · 검색어 · 시험 입력 · 입력 설명 — 품 수치 그대로 | +| 원문대로 고침 · 결함 | 09-20~09-30 | 36 | 227 | 빠진 기계 줄 · 주 할증 · 칸 밀림 · 재료 검색어 · 일부 구현 | +| 절 누락 채움 | 09-30 | 8 | +115 | 원문 소절에 새 로직(PLAN 28-2a) | +| 화면 편집 | 10-01~02 | 3 | GX 3(지움) | 사용자 · 자체 파일 | + +- **정본 로직 줄을 사람 · 화면이 고친 커밋 = 0.** 자동 커밋(`auto:`)은 1 건(`7270f13c` · 09-19)이고, AI 가 기계 고르기를 넓힌 것임. +- 모든 브랜치 · 원격에서 로직 파일이 바뀐 커밋은 HEAD 안에 다 있음. 마지막 바뀜은 10-05(`bc094969` 체인톱 가격 연결). +- 못 본 곳 — 데스크탑 메인(임시 배포) 폴더에서 커밋하지 않은 화면 저장(다른 PC). 마지막 전역 깃 싱크(10-31)까지 로직 파일 바뀜은 없음. + +품 · 식 뜻이 바뀐 커밋: + +| 커밋 | 날 | 무엇 | 로직 · 예 | +|---|---|---|---| +| `eb7cc600` | 09-20 | **조종원 시간 환산 1/8 · 16/12 · 25/20(질의회신 · 다산소프트 대조 −40 % → 0 %)** · 빠진 기계 줄 16 자리 · 산림 9장 돈 로직 셋 새로 | 33 · GC000267~276 · GF000249~251 | +| `2e304f52` | 09-20 | PBD 타설깊이 0 나눔 · 합판거푸집 1회 합판율 | 2 · GC000185 · GC000211 | +| `a55d3966` | 09-20 | 전기용접봉 줄 원문대로 · 표 밖 조합 멈춤 풀기 | 12 | +| `8f5566e7` | 09-21 | 모래 · 자갈 단위중량 · 레미콘 강도 · 슬럼프 입력 | 18 · GF000156 · GF000157 | +| `d9a31061` | 09-21 | 돌쌓기 · 돌붙임 첫 고르기 = 원문 조합 | 2 · GF000219 · GF000223 | +| 28-2b 5 커밋(`a9a46311` 등) | 09-30 | 재료 검색어 잘못 연결 | 78 | +| 28-2c 6 커밋(`c105d7ff` 등) | 09-30 | 주 할증 · 공구손료 % · 단위 환산 | 62 | +| 28-2d 11 커밋(`434e65ad` 등) | 09-30 | 값 · 칸 밀림 · 계산식(원문 설계예와 같게) | 34 | +| 28-2e 6 커밋(`de9b3958` 등) | 09-30 | 일부 구현(빠진 줄 더함) | 32 | +| 28-2a 8 커밋(`d8e5723c` ~ `4d128335`) | 09-30 | 절 누락 채움 — GF000252~330 79 · GC001104~1139 36 | +115 | + +- 새로 채운 115 가운데 옛 로직과 같은 절을 나눈 것은 2 개뿐임(GF000256 9-7-1 깨기 ↔ 옛 집토 · GF000258 9-8-1 깨기 ↔ 옛 철근절단). 둘 다 원문 안의 다른 항목이라 겹치지 않음. + +## ④ 원문 대조 + +방법 — 로직 원문번호를 원문 md 머리(산림 · 건설 부문별 1,780 개)와 자동으로 짝지음. 위험 묶음은 원문 글을 직접 읽음. + +자동 짝: + +| 닿는 곳 | 로직 | +|---|---| +| 같은 번호 머리 | 1,411 | +| 윗 절 머리 + 원문 안 항목(이름 낱말이 원문에 다 있음) — 측량 9장 44 · 산림 12-11-2 · 12-17-1 4 | 48 | +| 장 규정(공통 8장 기계경비) | 10 | +| **원문에 없는 절** | **0** | + +절마다 로직 수: + +| 꼴 | 절 | 로직 | 판정 | +|---|---|---|---| +| 한 절에 로직 하나 | 1,114 | 1,114 | 가 1,111 · 라(엔진 꼴 작업량) 3 | +| 단가 1 + 작업량 짝 | 22 | 44 | 가 22(단가) · 라(엔진 꼴 작업량) 22 | +| 로직 수 ≤ 원문 표 수 | 89 | 269 | 가 262 · 라 7(작업량 2 · 13-1-1 5) | +| 로직 수 > 원문 표 수 | 11 | 42 | 가 29(9 절) · 라 13(공통 8 기계경비 10 · 13-3-1 3) | + +위험 묶음 · 표본 대조: + +| 로직 키 | 원문 자리 | 판정 | 근거 | +|---|---|---|---| +| GF000101 · 103 · 105 · 107 · 109 · 111 · 265~276(18) | 산림 9-13-1~18 구조물터파기(`원문/산림_표준품셈/본문/제09장_토공/9-13_구조물터파기.md`) | 가 | 원문 소절 18(육상 · 용수 × 토사 · 암절취 · 발파암 × 깊이 3) ↔ 단가 로직 18 이 하나씩 짝. 표 QF000270~287 이 자기 소절 표 · 「~와 동일」은 앞 소절 표를 바로 가리킴. 용수 암절취는 할석공이 없는 것도 원문대로. 표 18 개 모두 원문 대조 「끝남」 | +| GF000100 · 106 | 9-13-1 · 9-13-4 작업량(Q 식) | 라(엔진 꼴) | 원문 표 k · f · E · ㎝ 로 Q 를 셈. 단가 로직이 부름 | +| GF000102 · 104 · 108 · 110 | 9-13-2 · 3 · 5 · 6 작업량 | **라(복제)** | 원문은 장비 「육상(용수)토사(0~1m)와 동일」. 로직 몸이 GF000100 · 106 과 글자까지 같음 | +| (9-13 꼴) | 9-13 머리 「※ 관보호공 · 관부설 보통인력 30 % 가산」 | 꼴 어긋남 | 1~6(09-19 작성) = 작업량 짝 · 비고에 30 % 미반영 적음. 7~18(09-30 작성) = 작업량을 안에 품음 · 30 % 언급 없음. 비율 표도 CF000127 · CF000128 두 벌(값 같음 · 원문 머리는 하나). 금액은 같음 | +| GF000099 · 263 · 264 | 산림 9-12-1~3 측구터파기 | 가 | 소절 셋 ↔ 로직 셋(+ 작업량 GF000098) | +| GF000219~222 · 324 | 산림 13-4-1 · 2 · 4 · 5 · 6 돌쌓기 | 가 | 소절마다(메 · 찰 × 인력 · 장비 · 밑돌) · 뒷길이 · 돌 · 쌓기 입력 · 높이 증가율(주③)도 원문대로 | +| GF000215 · 216 · 217 | 산림 13-3-1 기초다짐(인력) | **라(임의)** | 원문 표 하나(㎥당 · 보통인부만 · 다섯 줄). 모래 · 자갈은 입력으로 한 로직에 묶고 조약돌 · 뒤채움만 따로 뗌 | +| GF000218 | 산림 13-3-2 기초다짐 · 뒷채움(기계) | 가 | 표 줄 · 칸 그대로(플레이트 1.54 → 1.5ton 기계 대응만 비고) | +| GF000124 · 125 · 126 | 산림 9-19-1 토사면 고르기 | 가 | 원문 「1. 절토면」 · 「2. 성토면」(인력시공 · 기계시공 줄) | +| GF000076~079 | 산림 8-3 소각 · 매몰 · 훈증 · 박피 | 가 | 원문 한 표의 열 = 서로 다른 작업 · 단위(㎥/인 · RM/인 · 본/인). 단, 열 8 가운데 로직은 4(훈증 · 박피 빠짐) | +| GF000094 · 095 · 256 | 산림 9-7-1 무근콘크리트 깨기 | 가 | 깨기(본 표) · 집토(주⑥ 굴착기 능력) — 원문 안 두 일 | +| GF000243 · 244 | 산림 13-14 식생토낭 및 포트 | 가 | 토낭 = 표 세 줄 합 · 포트 = 주④ | +| GF000005 · 006 | 산림 3-4-3 운반로 보수비 | 가 | 보수공정표(4.) · 상용인부(2.) — 원문 안 두 일 | +| GF000134 · 135 · 136 | 산림 10-3-1~3 골재운반 | 가 | 소절 셋 · 로직 몸 같음(차이 = 설계자 입력 qt) | +| GC000467~472 | 토목 9-3-3 택지조성측량 | 가 | 원문 표 여섯(촌락 가 · 나 · 다 · 구릉 가 · 나 · 다 — 면적 · 축척 · 방안 다름) | +| GC000473~477 · 482~485 · 490~513 | 토목 9-3-4 · 9-4-3 · 9-5-4 측량 | 가 | 원문 안 번호 항목(「4. 계획가로 가구확정 계산측량」 등) 이름 그대로 | +| GC000085~087 · 088~090 | 공통 3-2-3 흙깎기 · 3-2-4 터파기(기계) | 가 | 토질마다 표(가. 보통토사 · 나. 혼합토사 · 다. 암) | +| GC000442~450 | 토목 8-4-1 · 8-4-2 천공 | 가 | 지층마다 표 | +| GC000829~833 | 기계설비 9-5-1 시운전 | 가 | 원문 한 표 · 줄마다 다른 작업과 단위(m · 대 · 식 · ㎡) | +| GC000806 · 807 | 기계설비 8-3-1 보일러 | 가 | 한 표 · 단위 다름(인/절 · 인/중량톤) | +| GC000217~221 · GC001081~1084 | 공통 6-3-6 슬립폼 · 유지관리 2-2-13 도상갱환 | 가 | 소절 · 열(설치 · 해체 / 개당 · m당)마다 | +| GC000229~235 | 공통 6-4-1 PSC빔 | 가 | 원문 표 일곱 | +| GC000924~926 | 기계설비 13-5-12 펌프 | 가 | 원문 1. · 2. · 3. 소항목 | +| GC000871~875 | 기계설비 13-1-1 플랜트 배관 | **라(임의)** | 원문은 한 표(칸 짜임 같음)가 인쇄 쪽으로 나뉨. 로직 다섯이 쪽 경계(6∼300 · 350∼600 · 600∼850 · 850∼1500+KSD3562 6∼10 · KSD3562 15∼500)를 그대로 따름. 한 로직에 관 종류 둘이 섞임 | +| GC000267~276 | 공통 8장 기계경비 | 라(엔진 꼴) | 8-1~8-4 규정 한 벌을 기종 경우(손료 보정 · 조종원 없음 · 손료만 · 부착물 8-4-8 주⑤)로 나눔 · 부르는 로직 500 | +| GC000267~271 · 275 · 276 · GF000145 | 조종원 시간당 노무비 | **다(본문 밖 근거)** | `원문/질의회신/대한건설협회/2001-05-29_노무비산출_관련_회신.md` — 회신 스스로 「표준품셈 등에서는 일률적으로 정하고 있지 않음」 · 품셈 8장 본문에 16/12 · 25/20 없음 | +| GC000316~320 ↔ GC001047~1051 | 토목 1-8-9 ↔ 유지관리 2-1-15 차선도색 | 가 | 원문이 두 부문에 같은 절을 둠(표 7 씩) | +| GC000382 · 383 등 (인력)/(장비) 45 | 토목 6-2-1 · 산림 13-4 등 | 가 | 표본 확인 — 원문 표 · 소절이 인력 · 장비 따로 | +| 로직 1,469 의 출처 칸 | — | — | 품셈 절 · 주 · 한전 요금표(값) 밖 출처는 질의회신 8 줄뿐 | + +## ⑤ 갈래별 수 · 권하는 처리 · 쓰는 곳 + +쓰는 곳 = M02 층 일위대가 · 사전조합 · 승격 문서(시스템 10 · 회사 16 · 개인 89 파일)와 프로젝트 사본(templates 가 있는 프로젝트 13). 직접 담은 로직은 모두 49 개임. + +| 갈래 | 수 | 목록 | 권하는 처리 | 쓰는 곳 | +|---|---|---|---|---| +| (가) 원문 그대로 · 갈라짐도 원문 짜임 | **1,424** | 한 절 하나 1,111 · 소절 · 표 · 항목 · 열마다 313 | 그대로 둠 · 목록에서 같은 절 · 윗 절 묶음으로 접어 보이기 | 49 개 전부(예 구조물터파기 GF000101 = 시스템 1 · 개인 2 · 프로젝트 13) | +| (나) 원문에 없는 덧 로직 | **0** | (화면 덧 GX 3 은 10-02 지움) | — | — | +| (다) 원문과 다르게 바뀐 로직 | 사람이 바꾼 것 **0** · 본문 밖 근거 **8**(위 가 · 라와 겹쳐 셈) | GC000267 · 268 · 269 · 270 · 271 · 275 · 276 · GF000145 | 사용자 판단 — 질의회신을 기준으로 인정하면 그대로 · 아니면 품셈 본문대로(하루 노임 그대로)로 되돌림(되돌리면 조종원 값이 크게 바뀜) | 직접 0 · 이 8 을 부르는 로직 471 · 프로젝트 사본 언급 10 | +| (다') 원문이 덜 정한 곳을 AI 가 해석 | 「해석:」 728 · 주 미반영 13 · 결정목록 127 물음(흠 365 줄 · 임도 23 물음) | `docs/raw/M01_결정목록/결정목록.md` | 결정목록에 답을 받아 그대로 반영 — 「바뀐 것」이 아니라 「정할 것」임 | — | +| (라-1) 엔진 꼴로 나눔(까닭 있음) | **37** | 작업량 27(산림 26 · 건설 1) · 기계경비 10 | 그대로 둠 · 목록에서 부르는 단가 로직 밑으로 접기 | 직접 0 · 단가 로직이 부름 | +| (라-2) 원문 한 항목을 임의로 쪼갬 | **8**(+ 복제 4 는 라-1 과 겹침) | GC000871~875 · GF000215~217 · (복제 GF000102 · 104 · 108 · 110) | 합침 후보 — 플랜트 배관 5 → 1(입력 = 관 종류 · 규격) · 기초다짐 3 → 1(입력 = 종별) · 작업량 복제 4 → GF000100 · GF000106 을 부르게 하고 키 폐기 | **0**(M02 · 프로젝트 · 다른 로직 모두 안 씀. 복제 4 는 짝 단가 로직 하나씩만 부름) | + +덤(갈래 밖 · 빠짐): +- 산림 8-3 방제 — 원문 열 8 가운데 로직 4(무더기 · 그루터기 훈증, 그루터기 · 원목 박피 없음). +- 결정목록 「로직 새로 만들기」 물음 6(Q001~Q006). + +## ⑥ 사용자에게 물을 것 + +1. **정본 쓰기 길을 막을지.** 지금은 시스템 관리자가 M01 화면 · 흐름도 [저장]으로 정본 로직 · 표를 고치고 지우고 더할 수 있음(이력 없음). 불변 기준값이라면 화면 쓰기를 막고, 고칠 일은 M02 단가산출로 옮기기를 권함. +2. **질의회신 근거 조종원 환산(1/8 · 16/12 · 25/20)을 기준으로 인정할지.** 기계경비 8 로직 · 부르는 로직 471 이 여기에 닿음. +3. **임의로 쪼갠 8 개 + 복제 4 를 정본에서 합칠지, 목록에서만 묶을지.** 쓰는 곳이 0 이라 지금 합쳐도 M02 · 프로젝트에는 영향 없음. +4. **9-13 꼴 맞춤** — 1~6 · 7~18 을 한 꼴로, 30 % 가산 비고와 중복 비율 표(CF000128)도 정리할지. 금액은 그대로임. diff --git a/docs/raw/요구_내역서/사용자요구_내역서.md b/docs/raw/요구_내역서/사용자요구_내역서.md index ebf82d19f..78a78e84a 100644 --- a/docs/raw/요구_내역서/사용자요구_내역서.md +++ b/docs/raw/요구_내역서/사용자요구_내역서.md @@ -106,3 +106,9 @@ M02 구조물/도면/일위대가 관련 6. 장비 줄(기사 · 기름 · 손료) 보이는 꼴 → 이건 EST 프로그램에서 어떻게 했는지 확인해봐. 7. 공사(B09) 단가산출근거 탭 → 같은 보기로 바꿈 8. M01 텍스트 수식 「변경」 뜻 → 시험 계산 값만 바꿈 + +## 내역 15 (11-01 · 사용자 · 내역 14-5 갈린 로직 원문 대조 결과 물음 셋에 답) + +1. 기계경비 로직 8 의 조종원 시간 환산(1/8 · 16/12 · 25/20 · 대한건설협회 질의회신 2001 근거 · 부르는 로직 471) → 빼고 본문대로 +2. 원문 한 표를 임의로 쪼갠 로직 12(플랜트 배관 5 · 기초다짐 3 · 작업량 복제 4 · 쓰는 곳 0) → 원문대로 합침(방제 8-3 빠진 로직 4 도 채움) +3. 구조물터파기(9-13) 로직 18 의 꼴 섞임 → 한 꼴로 맞춤 From ccc2fe080fd28a3835dcceae7ccad899d5395a95 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 15:04:41 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat(716-3):=20M02=20=EC=99=BC=EC=AA=BD=20?= =?UTF-8?q?=EC=B9=B8=20=EC=B0=A8=EB=A1=80=20=C2=B7=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=C2=B7=20=EA=B5=AC=EB=B6=84=EC=84=A0=20=E2=80=94=20=EC=9D=BC?= =?UTF-8?q?=EC=9C=84=EB=8C=80=EA=B0=80=20=E2=80=BA=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=A1=B0=ED=95=A9=20=E2=80=BA=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=84=A0=20=E2=80=BA=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=C2=B7=20=EC=98=B5=EC=85=98=20=EC=A4=84=20=EB=BA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 일위대가(옛 사전조합) 제목 locale 키 M02_PrecomboTitle · 나무 안 글 · 마우스 오른쪽 메뉴 · 빈 글을 일위대가로 - 구조물 옵션 정의 읽기 줄 뺌(M01 에 입구 있음) · open=옵션 은 M01 로 그대로 돌려보냄 - 시험: PRECOMBO_TITLE 상수 기대 → locale 키 기대로 고침 · 칸 차례 시험 더함 Co-Authored-By: Claude Sonnet 5.5 --- .../M02_MasterTemplete_Labor_Text.ts | 12 ++-- .../M02_MasterTemplete_UI_Side.ts | 63 +++++++------------ .../M02_MasterTemplete_UI_Side_Precombo.ts | 11 ++-- resources/tester/test_m02_precombo_scope.py | 19 +++++- ui_template/ui_template_locale_m2.ts | 1 + 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts index d64dbdfc9..3e898c675 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts @@ -101,7 +101,7 @@ const TEXT = { Labor_LineApplied: ["반영(수량 곱함)", "Applied (× quantity)"], Labor_LineBase: ["기준", "Base"], Labor_FromLogic: ["원 로직", "Source logic"], - Lib_Precombo: ["사전조합", "Pre-combination"], + Lib_Precombo: ["일위대가", "Unit-cost item"], Lib_System: ["마스터", "Master"], Lib_Company: ["회사", "Company"], Lib_Personal: ["개인", "Personal"], @@ -156,13 +156,13 @@ const TEXT = { In_Close: ["닫기", "Close"], Lib_Name: ["이름", "Name"], // 서고 목록표(51-4) - Lib_PrecomboAll: ["일위대가 사전조합", "Pre-combination"], - Lib_None: ["사전조합 없음", "No pre-combination"], + Lib_PrecomboAll: ["일위대가", "Unit-cost items"], + Lib_None: ["일위대가 없음", "No unit-cost items"], Lib_Unsorted: ["미분류", "Unsorted"], - Lib_NewHere: ["여기에 새 사전조합", "New pre-combination here"], + Lib_NewHere: ["여기에 새 일위대가", "New unit-cost item here"], Lib_FolderFull: [ - "폴더 안에 사전조합 {n}개 — 먼저 옮기거나 지운 뒤 지움", - "Folder holds {n} pre-combination(s) — move or delete them first", + "폴더 안에 일위대가 {n}개 — 먼저 옮기거나 지운 뒤 지움", + "Folder holds {n} unit-cost item(s) — move or delete them first", ], Lib_FolderDelete: ["「{value}」 빈 폴더를 지움", "Delete empty folder “{value}”"], Lib_Create: ["생성", "Create"], diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts index b6b5fc63e..7f1d5b88d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts @@ -1,8 +1,9 @@ /* ============================================================================= * M02_MasterTemplete_UI_Side.ts - * 마스터 템플릿 좌측 패널 — 「구조물 조합」(층 단추 · 나무) · 「일위대가 사전조합」(층 단추 따로 · 2단 폴더 나무 · - * `UI_Side_Precombo` · 50-3 · 134-3) · 「일위대가 실무자료」(구조물 조합 층 · `Hopyo_Side` · 324-4) · - * 「도면 양식」(새로 · 본떠 · 지우기). + * 마스터 템플릿 좌측 패널 차례(716-3) — 「일위대가」(층 단추 따로 · 2단 폴더 나무 · `UI_Side_Precombo` · + * 50-3 · 134-3) › 「구조물 조합」(층 단추 · 나무) › 수평 구분선 › 「일위대가 불러오기」(구조물 조합 층 · + * `Hopyo_Side` · 324-4) › 「도면 양식」(새로 · 본떠 · 지우기) › 실무문서. 「단가산출」 칸은 맨 위(717-2a). + * 구조물 옵션 정의는 M01 로 옮김(716-1). * 서버 길은 계약 `6_계약.md`. * 구조물 도면 · 일위대가 · 면적산출 상세는 좌측 줄이 아니라 항목 카드 [수정]으로(PLAN 45-2). * 화면 이름 「구조물 조합」(PLAN 26-7a) — 파일 이름 · 양식 키 · API 길은 「구조물집계표」 그대로. @@ -30,17 +31,13 @@ import { type TemplateInfo, } from "./M02_MasterTemplete_Api_Fetch"; import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure"; -import { buildOptionsSection } from "./StructOptions/M02_SO_Options"; import { buildPracticeSection } from "./M02_MasterTemplete_Practice"; import { HOPYO_BUILT } from "./M02_MasterTemplete_Hopyo_Check"; import { buildHopyoSection } from "./M02_MasterTemplete_Hopyo_Side"; import { canEdit, type Selection } from "./M02_MasterTemplete_UI_Main"; -import { - buildPrecomboSection, - PRECOMBO_TITLE, - type PrecomboOptions, -} from "./M02_MasterTemplete_UI_Side_Precombo"; +import { buildPrecomboSection, type PrecomboOptions } from "./M02_MasterTemplete_UI_Side_Precombo"; import { createEmpty } from "@ui/ui_template_empty"; +import { createDivider } from "@ui/ui_template_divider"; const KINDS: { kind: Kind; label: () => string }[] = [ { kind: "table", label: () => L("M02_KindTable") }, @@ -68,7 +65,7 @@ export interface SideOptions { showView: (label: string, node: HTMLElement) => (dirty: boolean) => void; /** 「구조물 조합」 줄 아래 — 층 단추 · 고른 층의 구조물 라이브러리 나무(PLAN 29-2 · 페이지가 만듦) */ tree?: HTMLElement[]; - /** 「일위대가 사전조합」 층(134-3 · 페이지 층과 따로 · 층 단추는 페이지가 만듦) */ + /** 「일위대가」 층(134-3 · 페이지 층과 따로 · 층 단추는 페이지가 만듦) */ precombo: Pick; } @@ -77,7 +74,7 @@ export interface SideHandle { refresh: () => Promise; /** 고름 표시를 지금 열린 것 한 줄로 — 편집 칸이 무엇을 열든(카드 [도면 수정] 등) 부름 */ paint: () => void; - /** 진입 해시 `open`(PLAN 27-1d) — 그 컨테이너를 펼침(옵션은 열기까지) · 옵션 · 도면은 시스템 층에만 */ + /** 진입 해시 `open`(PLAN 27-1d) — 그 컨테이너를 펼침 · 도면은 시스템 층에만 · 옵션은 M01 로 옮겨 아무 일 없음 */ reveal: (target: "옵션" | "도면" | "일위대가조합") => void; /** 사전조합 층이 바뀜(134-3) — 그 컨테이너만 다시 그림 */ refreshPrecombo: () => Promise; @@ -120,13 +117,6 @@ export function buildSide(opt: SideOptions): SideHandle { del.addEventListener("click", () => void deleteCurrent()); return [el("div", { className: "m02-side__row", children: [btnNew, copy, del] })]; }; - // 구조물 옵션 보이기 — 구조물 집계 양식 아래 별도 줄 하나(누르면 메인 칸에 종류별 옵션 체크) - const options = buildOptionsSection({ - isAdmin: canEdit(layer, opt.isAdmin), - confirmLeave: opt.confirmLeave, - show: opt.showView, - onOpen: () => (practice?.clear(), paint()), - }); /** 컨테이너 제목을 펼치면 그 화면을 엶 — 같은 이름 줄 단추를 갈음(PLAN 29-6) */ const openOnExpand = (sec: HTMLElement, run: () => void): HTMLElement => { sec.querySelector(".ui-collapsible__title")?.addEventListener("click", () => { @@ -143,27 +133,24 @@ export function buildSide(opt: SideOptions): SideHandle { openOnExpand(summary, async () => { if (!isPicked("table", STRUCTURE_TABLE) && (await opt.confirmLeave())) opt.onOpen(null); }); - // 일위대가 사전조합 서고(50-3 · 134-3) — 구조물 조합 바로 아래 · 층은 페이지 층과 따로 + // 일위대가 서고(50-3 · 134-3) — 맨 위 칸 · 층은 페이지 층과 따로 const precombo = buildPrecomboSection({ ...opt.precombo, confirmLeave: opt.confirmLeave, onOpen: opt.onOpen, getOpen: opt.getOpen, }); - const precomboSec = section(PRECOMBO_TITLE, precombo.body); - // 실무자료로 사전조합을 만들었음(확인 창 · 엑셀 보기 창) → 목록 다시 · 이 패널이 내려갔으면 듣기 끝 + const precomboSec = section(L("M02_PrecomboTitle"), precombo.body); + // 실무자료로 일위대가를 만들었음(확인 창 · 엑셀 보기 창) → 목록 다시 · 이 패널이 내려갔으면 듣기 끝 const onBuilt = (): void => precombo.body.isConnected ? void precombo.refresh() : document.removeEventListener(HOPYO_BUILT, onBuilt); document.addEventListener(HOPYO_BUILT, onBuilt); - // 구조물 옵션 정의 · 도면 양식은 시스템 층(마스터 템플릿)에만 · 정의 고쳐 씀은 시스템 관리자만 · 다른 역할은 읽기만(PLAN 48-2) - const extras = + // 도면 양식은 시스템 층(마스터 템플릿)에만 + const drawing = layer === "system" - ? { - 옵션: section(options.title, options.body), - 도면: section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing")), - } + ? section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing")) : null; // 실무문서(옛 마스터 일위대가 조합 · PLAN 39-2) — 구조물 조합 밖 · 도면 양식 아래 · 마스터 층 · 시스템 관리자만 const practice = @@ -171,23 +158,24 @@ export function buildSide(opt: SideOptions): SideHandle { ? buildPracticeSection({ confirmLeave: opt.confirmLeave, show: opt.showView, - onOpen: () => (options.clear(), paint()), + onOpen: paint, }) : null; - // 일위대가 실무자료(324-4) — 사전조합 아래 · 층 = 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐 + // 일위대가 불러오기(324-4 · 제목은 718-1) · 층 = 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐 const hopyo = buildHopyoSection({ canWrite: canEdit(layer, opt.isAdmin), confirmLeave: opt.confirmLeave, show: opt.showView, - onOpen: () => (options.clear(), practice?.clear(), paint()), + onOpen: () => (practice?.clear(), paint()), }); const listHost = el("div", { className: "m02-side__lists ui-sidebar-panel", children: [ - summary, precomboSec, + summary, + createDivider(), section(hopyo.title, hopyo.body), - ...(extras ? [extras.옵션, extras.도면].filter((s): s is HTMLElement => !!s) : []), + ...(drawing ? [drawing] : []), ...(practice ? [section(practice.title, practice.body)] : []), ], }); @@ -237,14 +225,13 @@ export function buildSide(opt: SideOptions): SideHandle { for (const [kind, b] of editButtons) b.copy.disabled = b.del.disabled = cur?.종류 !== kind; } - /** 고름 표시는 여기서만 — 목록 · 옵션 줄을 함께 그려야 두 줄이 동시에 안 남음 - * (양식이 열려 있으면 옵션 · 일위대가 조합 고름은 지움) */ + /** 고름 표시는 여기서만 — 목록 · 실무문서 줄을 함께 그려야 두 줄이 동시에 안 남음 + * (양식이 열려 있으면 실무문서 고름은 지움) */ function paint(): void { paintList(); precombo.paint(); hopyo.paint(); paintButtons(); - if (opt.getOpen()) options.clear(); if (opt.getOpen()) practice?.clear(); } @@ -347,10 +334,8 @@ export function buildSide(opt: SideOptions): SideHandle { } const reveal = (target: "옵션" | "도면" | "일위대가조합"): void => { - const sec = target === "일위대가조합" ? precomboSec : extras?.[target]; - if (!sec) return; // 옵션 보이기는 시스템 관리자만 - sec.classList.remove("is-collapsed"); - if (target === "옵션") options.body.querySelector("button")?.click(); + const sec = target === "일위대가조합" ? precomboSec : target === "도면" ? drawing : null; + sec?.classList.remove("is-collapsed"); }; void refresh(); diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts index e109cc347..38125c298 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Side_Precombo.ts @@ -1,9 +1,9 @@ /* ============================================================================= * M02_MasterTemplete_UI_Side_Precombo.ts - * 좌측 「일위대가 사전조합」 컨테이너(50-3 ③ · 51-4 · 134-3) — 구조물 조합 아래 · 층 단추(페이지가 만듦 · - * 페이지 층과 따로 · 마스터 · 회사 · 내 개인) + 그 층 사전조합 나무. + * 좌측 「일위대가」 컨테이너(50-3 ③ · 51-4 · 134-3) — 구조물 조합 아래 · 층 단추(페이지가 만듦 · + * 페이지 층과 따로 · 마스터 · 회사 · 내 개인) + 그 층 일위대가 나무. * 나무 = 자유 폴더(158-1 · 구조물 조합 탐색기와 같은 꼴 · 깊이 자유 · 층마다 따로 · 자리 없는 문서 = 뿌리) — - * 폴더 오른쪽 클릭 [새 폴더] · [여기에 새 사전조합] · [이름 바꾸기] · [지우기](빈 폴더만) · 빈 자리 오른쪽 + * 폴더 오른쪽 클릭 [새 폴더] · [여기에 새 일위대가] · [이름 바꾸기] · [지우기](빈 폴더만) · 빈 자리 오른쪽 * 클릭 = 뿌리 메뉴 · 문서 · 폴더 끌어 옮기기. 폴더 자리만 서버(`precombo-tree`)에 적음 — 문서 파일은 안 옮김. * 구분 · 상세구분은 문서 칸(상세 머리 · 고르기 창 거름 · `precomboClasses`) — 나무 폴더와 무관. * 문서 누름 = 편집 칸(UI_Main 종류 precombo = 일위대가 상세 모드) · 켜진 층 단추 다시 누름 = 목록표 @@ -42,7 +42,6 @@ import { createEmpty } from "@ui/ui_template_empty"; export type PrecomboKind = Extract; -export const PRECOMBO_TITLE = "일위대가 사전조합"; export const isPrecombo = (kind: Kind): kind is PrecomboKind => kind === "precombo"; /** 여러 구조물에 쓰는 묶음(콘크리트 · 거푸집 등) — 군마다 첫 상세구분(134-5) */ @@ -183,7 +182,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { } }; - /** [여기에 새 사전조합] — 뿌리에 생성 → 폴더면 그 자리로 옮김 → 나무 다시 · 상세 엶 */ + /** [여기에 새 일위대가] — 뿌리에 생성 → 폴더면 그 자리로 옮김 → 나무 다시 · 상세 엶 */ const make = (path: string[]): void => { const at = opt.scope(); nameDialog(tc("Lib_NewHere"), "", async (name) => { @@ -303,7 +302,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle { paint(); }; - // 빈 자리 오른쪽 클릭 = 뿌리 메뉴(새 폴더 · 새 사전조합) + // 빈 자리 오른쪽 클릭 = 뿌리 메뉴(새 폴더 · 새 일위대가) treeHost.addEventListener("contextmenu", (event) => { if (!view || (event.target as HTMLElement).closest("[data-node]")) return; event.preventDefault(); diff --git a/resources/tester/test_m02_precombo_scope.py b/resources/tester/test_m02_precombo_scope.py index fe9bd01fa..081da91c9 100644 --- a/resources/tester/test_m02_precombo_scope.py +++ b/resources/tester/test_m02_precombo_scope.py @@ -82,7 +82,7 @@ def test_사전조합_층_단추는_페이지를_다시_안_세움(): def test_고름에_owner_권한_서고_길에_명시_owner(): side = _text("M02_MasterTemplete_UI_Side_Precombo.ts") - assert 'export const PRECOMBO_TITLE = "일위대가 사전조합";' in side + assert "PRECOMBO_TITLE" not in side # 716-3 — 제목은 locale 키 「일위대가」 assert "owner: scope.owner," in side and "canWrite: scope.canWrite," in side assert "listTemplates(at.layer, null, at.owner)" in side main = _text("M02_MasterTemplete_UI_Main.ts") @@ -133,3 +133,20 @@ def test_목록표_빈_구분_미분류_고르기_창_새_분류_거름(): # 거름은 칸 값 그대로 — 품셈 구분을 고르면 서고 줄 빠지고 새 분류를 고르면 로직 줄 빠짐 assert "(!sub.select.value || x.row.구분 === sub.select.value)" in pick assert "(!sub.select.value || x.구분 === sub.select.value)" in pick + + +def test_왼쪽_칸_차례_일위대가_구조물조합_구분선_불러오기_716_3(): + side = _text("M02_MasterTemplete_UI_Side.ts") + order = side[side.index("m02-side__lists") :] + marks = [ + "precomboSec,", + "summary,", + "createDivider()", + "hopyo.title", + "drawing ?", + "practice ?", + ] + at = [order.index(m) for m in marks] + assert at == sorted(at) + + assert 'L("M02_PrecomboTitle")' in side and "buildOptionsSection" not in side diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index 0639ba79f..ce9385148 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -189,4 +189,5 @@ export const ui_locales_m2 = { "Practice documents changed — reloaded", ], B01_Dashboard_CardPrecombo: ["일위대가", "Unit-cost items"], + M02_PrecomboTitle: ["일위대가", "Unit-cost items"], } as const;