From dda5f6233f5fb3f6731c4debb2f7d0cd62a1b268 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:49:43 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EF=BB=BFfeat(M02):=20=EC=84=9C=EA=B3=A0=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20=C2=B7=20=EC=83=81=EC=84=B8=EA=B5=AC?= =?UTF-8?q?=EB=B6=84=20=C2=B7=20=EC=97=B0=EA=B2=B0=20=ED=99=94=EB=A9=B4(51?= =?UTF-8?q?-12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서고 목록표에 구분 · 상세구분 칸(키 뒤) - 단가승격 상세 정보 칸 = 구분 · 상세구분 · 연결(원 로직 키 · 출처) 읽기만 - 사전조합 상세 정보 칸 = 구분 · 상세구분 고르기(로직 구분 목록 logic-lib/subs · 구분 바꾸면 상세구분 = 그 첫 줄) - 새 사전조합 처음 값 = 로직 구분 목록 첫 줄 - 로직 고르기 창 구분 · 상세구분 거름에 승격 · 사전조합 줄도 걸림 - 화면 시나리오 51-12 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- .../M02_MasterTemplete_Labor_Api.ts | 3 + .../M02_MasterTemplete_Labor_Detail.ts | 80 +++++++++- .../M02_MasterTemplete_Labor_Pick.ts | 12 +- .../M02_MasterTemplete_Labor_Text.ts | 2 + .../M02_MasterTemplete_Library_List.ts | 53 +++++-- resources/tester/ui_checks/51-12_서고_구분.js | 142 ++++++++++++++++++ 6 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 resources/tester/ui_checks/51-12_서고_구분.js diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts index ebf4db18c..d4c4bf6c6 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Api.ts @@ -233,6 +233,9 @@ export interface LibraryRow { 이름: string; 제목?: string | null; 단위?: string | null; + /** 51-12 — 승격 = 원 로직 것 · 사전조합 = 고른 것 */ + 구분?: string | null; + 상세구분?: string | null; } /** 서고 목록 — 그 층(주인) 단가승격 `promote` · 사전조합 `precombo` 문서 */ export const fetchLibrary = async ( diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts index d0bbc892b..e38ba6874 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts @@ -9,15 +9,23 @@ * · 같은 로직 두 번 · 같은 자재 두 번 막음 · 빈 일위대가도 저장 됨 * · 오른쪽 「원문」 접는 패널(47-4 · 상세만 · 공용 `ui_template_side_panels`) = 고른 줄 로직의 출처 원문 * · 서고 모드(50-3 · `mode`) = 단가승격 · 사전조합 — 이름 칸 · 단가산출 로직 표 · 원문만(자재 · 면적 · 다이어그램 없음) · + * 정보 칸 구분 · 상세구분(51-12 · 승격 = 읽기만 + 연결 · 사전조합 = 고름) · * 승격은 로직 한 줄(입력값 고정 · 더하기 · 순서 · 수량 없음) · 사전조합 = 일위대가 상세와 같은 [로직 추가] · 표 · 동작(51-11) · 수량 손 입력(기본 1) * ========================================================================== */ -import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements"; +import { + createButton, + createInputField, + createSelectField, + el, + showToast, +} from "@ui/ui_template_elements"; import { createSidePanels } from "@ui/ui_template_side_panels"; import { ROUTES } from "@config/config_frontend"; import { navigateTo } from "../A00_Common/router"; import { setPendingLogicKey } from "../M01_MasterData/M01_MasterData_UI_Side"; import { createSourceView } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Source"; +import type { SubBrief } from "../M01_MasterData/M01_MasterData_UI_Logic_Api"; import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet"; import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc"; import { @@ -33,6 +41,7 @@ import { lineId, settle, type LaborRef } from "./M02_MasterTemplete_Labor_Calc"; import { areaLinks, calcLabor, + fetchLibLogicSubs, fetchLogicInputs, normalizeLabor, type LaborCalc, @@ -457,6 +466,69 @@ export function mountLaborDetail( opt.onChanged(dirty()); }); + /** 서고 구분 · 상세구분(51-12) — 승격 = 원 로직 것 읽기만 + 연결(원 로직 키 · 출처) · + * 사전조합 = 로직 구분 목록(logic-lib/subs)에서 고름(구분 바꾸면 상세구분 = 그 첫 줄) */ + const meta = cur as { 구분?: string | null; 상세구분?: string | null; 연결?: unknown }; + const readField = (label: string, field: string, value: string): HTMLElement => { + const f = createInputField({ type: "text", label }); + f.root.dataset.laborField = field; + f.input.value = value; + f.input.readOnly = true; + return f.root; + }; + const libMeta = (): HTMLElement[] => { + if (promote) { + const link = (meta.연결 ?? {}) as { 로직?: string; 키?: string; 출처?: string }; + return [ + readField(tc("Sub"), "구분", meta.구분 ?? ""), + readField(tc("Detail"), "상세구분", meta.상세구분 ?? ""), + readField( + tc("Lib_Link"), + "연결", + [link.로직 ?? link.키, link.출처].filter(Boolean).join(" · "), + ), + ]; + } + const sub = createSelectField({ options: [], label: tc("Sub"), disabled: !edit }); + const detail = createSelectField({ options: [], label: tc("Detail"), disabled: !edit }); + sub.root.dataset.laborField = "구분"; + detail.root.dataset.laborField = "상세구분"; + let subs: SubBrief[] = []; + // 목록에 없는 지금 값도 선택지로 둠 + const opts = (names: string[], now: string | null | undefined) => + [...new Set([...(now ? [now] : []), ...names])].map((v) => ({ value: v, text: v })); + const fill = (): void => { + sub.setOptions( + opts( + subs.map((s) => s.name), + meta.구분, + ), + meta.구분 ?? "", + ); + const details = subs.find((s) => s.name === meta.구분)?.details ?? []; + detail.setOptions(opts(details, meta.상세구분), meta.상세구분 ?? ""); + }; + sub.select.addEventListener("change", () => { + meta.구분 = sub.select.value; + meta.상세구분 = subs.find((s) => s.name === meta.구분)?.details[0] ?? null; + fill(); + opt.onChanged(dirty()); + }); + detail.select.addEventListener("change", () => { + meta.상세구분 = detail.select.value; + opt.onChanged(dirty()); + }); + fill(); + void fetchLibLogicSubs().then( + (got) => { + subs = got; + fill(); + }, + () => undefined, + ); + return [sub.root, detail.root]; + }; + const editor = (body: HTMLElement): HTMLElement => side ? el("div", { className: "ui-side-panels-row", children: [body, side.root] }) : body; host.replaceChildren( @@ -473,7 +545,11 @@ export function mountLaborDetail( children: [ el("div", { className: "m01lab__info", - children: [...(lib ? [nameField.root] : []), unit.root, origin.root], + children: [ + ...(lib ? [nameField.root, ...libMeta()] : []), + unit.root, + origin.root, + ], }), ], }), diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts index 445b48d45..af891ca02 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts @@ -82,9 +82,13 @@ export function openLogicPicker(done: (logic: LogicSummary) => void, lib?: Libra (!detail.select.value || x.상세구분 === detail.select.value) && (!q || `${x.키} ${x.이름}`.toLowerCase().includes(q)), ); - const libFound = sub.select.value - ? [] - : libRows.filter((x) => !q || `${x.row.이름} ${x.row.제목 ?? ""}`.toLowerCase().includes(q)); + // 서고 줄도 구분 · 상세구분으로 거름(51-12) + const libFound = libRows.filter( + (x) => + (!sub.select.value || x.row.구분 === sub.select.value) && + (!detail.select.value || x.row.상세구분 === detail.select.value) && + (!q || `${x.row.이름} ${x.row.제목 ?? ""}`.toLowerCase().includes(q)), + ); const total = all.length + libRows.length; const shown = found.length + libFound.length; finder.hint.textContent = loading @@ -110,7 +114,7 @@ export function openLogicPicker(done: (logic: LogicSummary) => void, lib?: Libra key: x.row.이름, tag: tc(x.kind), name: x.row.제목 || x.row.이름, - sub: x.row.이름, + sub: `${x.row.이름} · ${x.row.구분 ?? ""} ${x.row.상세구분 ?? ""}`.trim(), unit: x.row.단위 ?? "", onPick: () => { close(); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts index c7be730db..9ac8e5424 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Text.ts @@ -124,6 +124,8 @@ const TEXT = { Lib_Unit: ["단위", "Unit"], Lib_Base: ["기준 금액", "Base amount"], Lib_Date: ["고친 날", "Modified"], + // 서고 구분 · 연결(51-12) + Lib_Link: ["연결", "Linked logic"], } as const satisfies Record; export type ComboTextKey = keyof typeof TEXT; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts b/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts index 3a183094e..4e729405d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Library_List.ts @@ -1,7 +1,7 @@ /* ============================================================================= * M02_MasterTemplete_Library_List.ts * 일위대가 조합 서고 목록표(51-4) — 좌측 항목(단가산출 승격 · 일위대가 사전조합)을 누르면 상세 칸에 뜸. - * 표 = 고름 · 키 · 이름 · 단위 · 기준 금액(서버 1 단위 계) · 고친 날 · 줄 누름 = 그 문서 상세. + * 표 = 고름 · 키 · 구분 · 상세구분(51-12) · 이름 · 단위 · 기준 금액(서버 1 단위 계) · 고친 날 · 줄 누름 = 그 문서 상세. * 머리 [생성] · [복사](고른 줄) · [삭제](고른 줄 · 쓰는 곳 있으면 서버 409 까닭 그대로). * 꼴 = M01 단가산출 로직 목록 — 찾기 줄(`buildHeadFilters`) · 쪽(`buildPager`) · 표(`m01-master__grid`) 를 M01 에서 import. * ========================================================================== */ @@ -30,12 +30,19 @@ import { type LibraryBody, type TemplateInfo, } from "./M02_MasterTemplete_Api_Fetch"; +import { fetchLibLogicSubs } from "./M02_MasterTemplete_Labor_Api"; import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick"; import { tc } from "./M02_MasterTemplete_Labor_Text"; import type { PrecomboKind } from "./M02_MasterTemplete_UI_Side_Precombo"; /** 목록 줄 — 서버가 문서 `이름`(제목) · 단위 · 1 단위 기준 계를 실어 보냄 */ -type Row = TemplateInfo & { 제목?: string | null; 단위?: string | null; 기준금액?: number | null }; +type Row = TemplateInfo & { + 제목?: string | null; + 단위?: string | null; + 기준금액?: number | null; + 구분?: string | null; + 상세구분?: string | null; +}; export interface LibraryListOptions { layer: Layer; @@ -99,9 +106,15 @@ export function mountLibraryList( attrs: { "data-lib-key": row.이름 }, children: [ pick, - ...[row.이름, row.제목 ?? "", row.단위 ?? "", money(row.기준금액), row.수정일].map( - (v) => el("td", { text: v, attrs: { title: v } }), - ), + ...[ + row.이름, + row.구분 ?? "", + row.상세구분 ?? "", + row.제목 ?? "", + row.단위 ?? "", + money(row.기준금액), + row.수정일, + ].map((v) => el("td", { text: v, attrs: { title: v } })), ], }); tr.addEventListener("click", () => opt.open(row.이름, libraryLabel(row))); @@ -109,7 +122,7 @@ export function mountLibraryList( }) : [ el("tr", { - children: [el("td", { text: L("M02_NoTemplates"), attrs: { colspan: "6" } })], + children: [el("td", { text: L("M02_NoTemplates"), attrs: { colspan: "8" } })], }), ]), ); @@ -164,7 +177,8 @@ export function mountLibraryList( const create = async (body: LibraryBody, label: string): Promise => made((await newLibrary(opt.layer, opt.kind, body)).이름, label); - /** 단가승격 = 단가산출 로직 고르기 → 이름 · 단위는 로직 것 · 사전조합 = 이름만 · 빈 묶음 */ + /** 단가승격 = 단가산출 로직 고르기 → 이름 · 단위 · 구분 · 연결은 로직 것(서버가 채움) · + * 사전조합 = 이름 + 구분 · 상세구분 처음 값 = 로직 구분 목록 첫 줄(51-12 · 상세에서 바꿈) · 빈 묶음 */ btnNew.addEventListener("click", () => { if (opt.kind === "promote") return openLogicPicker((x) => { @@ -177,9 +191,17 @@ export function mountLibraryList( showToast(L("M02_ActionFailed").replace("{value}", why(error)), "error"), ); }); - nameDialog(L("M02_NewTitle"), "", (name) => - create({ 문서: { 이름: name, 단위: null, 담은로직: [] } }, name), - ); + nameDialog(L("M02_NewTitle"), "", async (name) => { + const first = (await fetchLibLogicSubs())[0]; + const 문서 = { + 이름: name, + 구분: first?.name ?? null, + 상세구분: first?.details[0] ?? null, + 단위: null, + 담은로직: [], + }; + await create({ 문서 }, name); + }); }); /** 복사 = 같은 층 새 키(서버 · 본뜬 = 원래 키) → 고른 이름으로 고쳐 씀 */ @@ -221,7 +243,16 @@ export function mountLibraryList( page = 1; draw(); }); - const heads = ["", "Lib_Key", "Lib_Name", "Lib_Unit", "Lib_Base", "Lib_Date"] as const; + const heads = [ + "", + "Lib_Key", + "Sub", + "Detail", + "Lib_Name", + "Lib_Unit", + "Lib_Base", + "Lib_Date", + ] as const; host.replaceChildren( buildHeadFilters([searchField.root, pager]), el("div", { diff --git a/resources/tester/ui_checks/51-12_서고_구분.js b/resources/tester/ui_checks/51-12_서고_구분.js new file mode 100644 index 000000000..87d16fb80 --- /dev/null +++ b/resources/tester/ui_checks/51-12_서고_구분.js @@ -0,0 +1,142 @@ +/* 51-12 — 서고(단가승격 · 사전조합) 구분 · 상세구분 · 연결 + * 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=일위대가조합&root=master) · 시스템 관리자 + * 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌) + * 쓰기 없음 — GET · calc 밖 요청은 가짜 400(새로 만들기 POST 본문만 잡음) · 마스터 SP-000001 · SC-000001 읽기만 + * 기대 수치: + * - heads = ["", "키", "구분", "상세구분", "이름", ...] · listSP = SP-000001 줄 구분 · 상세구분 빈 것 아님 + * - promote = { 구분, 상세구분, 연결 } 셋 다 글 있음 · readOnly = true + * - precombo = { selects: 2, 구분 = 문서 값 · subOptions ≥ 1 } · switched.상세구분 = 바꾼 구분의 첫 상세구분 · back = 처음 값 + * - created = { 구분: 목록 첫 구분, 상세구분: 그 첫 상세구분 } + * - picker = 구분(SP-000001 것) 고르면 SP-000001 줄 보임 · 다른 구분이면 안 보임 + */ +(async () => { + const wait = (ms) => new Promise((r) => setTimeout(r, ms)); + const until = async (fn, ms = 6000) => { + for (let t = 0; t < ms; t += 100) { + const v = fn(); + if (v) return v; + await wait(100); + } + return null; + }; + const json = (body, status = 200) => + new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } }); + const real = window.fetch; + let posted = null; + window.fetch = async (input, init = {}) => { + const url = String(input?.url ?? input); + const method = (init.method ?? "GET").toUpperCase(); + if (method === "POST" && url.includes("/library/precombo")) { + posted = JSON.parse(init.body); + return json({ detail: "시험 — 쓰기 막음" }, 400); + } + if (method !== "GET" && !url.includes("/calc")) + return json({ detail: "시험 — 쓰기 막음" }, 400); + return real(input, init); + }; + const sec = [...document.querySelectorAll(".m02-side__lists > section")].find( + (s) => s.querySelector(".ui-collapsible__title")?.textContent?.trim() === "일위대가 조합", + ); + const items = [...sec.querySelectorAll("[data-precombo]")]; + const list = (kind) => document.querySelector(`[data-lib-list="${kind}"]`); + const edit = () => document.querySelector(".m02-main__host [data-labor]"); + const field = (name) => edit()?.querySelector(`[data-labor-field="${name}"]`); + const btn = (root, text) => + [...(root?.querySelectorAll("button") ?? [])].find((b) => b.textContent === text); + const subs = await ( + await real("/api/m02/logic-lib/subs", { credentials: "include" }) + ) + .json() + .then((d) => d.subs); + try { + // ① 승격 목록 · 상세 + items[0].click(); + const spRow = await until(() => list("promote")?.querySelector('[data-lib-key="SP-000001"]')); + const heads = [...list("promote").querySelectorAll("thead th")].map((th) => th.textContent); + const cells = [...spRow.querySelectorAll("td")].map((td) => td.textContent); + const listSP = { 구분: cells[2], 상세구분: cells[3] }; + spRow.querySelector("td:nth-child(5)").click(); + await until(() => field("연결")); + const val = (name) => field(name)?.querySelector("input")?.value ?? null; + const promote = { + 구분: val("구분"), + 상세구분: val("상세구분"), + 연결: val("연결"), + readOnly: ["구분", "상세구분", "연결"].every( + (n) => field(n)?.querySelector("input")?.readOnly, + ), + }; + // ② 사전조합 상세 — 고르기 · 바꿔 보고 되돌림 + items[1].click(); + ( + await until(() => + list("precombo")?.querySelector('[data-lib-key="SC-000001"] td:nth-child(5)'), + ) + ).click(); + const subSel = await until(() => field("구분")?.querySelector("select")); + await until(() => subSel.options.length > 1, 4000); + const detSel = field("상세구분").querySelector("select"); + const start = { 구분: subSel.value, 상세구분: detSel.value }; + const precombo = { + selects: edit().querySelectorAll(".m01lab__info select").length, + ...start, + subOptions: subSel.options.length, + }; + const other = subs.find((s) => s.name !== start.구분 && s.details.length); + subSel.value = other.name; + subSel.dispatchEvent(new Event("change")); + const switched = { + 구분: field("구분").querySelector("select").value, + 상세구분: field("상세구분").querySelector("select").value, + want: other.details[0], + }; + const s2 = field("구분").querySelector("select"); + s2.value = start.구분; + s2.dispatchEvent(new Event("change")); + const d2 = field("상세구분").querySelector("select"); + d2.value = start.상세구분; + d2.dispatchEvent(new Event("change")); + const back = { + 구분: field("구분").querySelector("select").value, + 상세구분: field("상세구분").querySelector("select").value, + }; + // ③ 고르기 창 — 구분 거름에 승격 줄 + btn(edit(), "로직 추가").click(); + const pick = await until(() => document.querySelector(".m01-logic__pick")); + await until(() => pick.querySelector('[data-pick="SP-000001"]'), 20000); + const [fSub, fDet] = pick.querySelectorAll(".ui-find__filters select"); + const pickerWith = async (s, d) => { + fSub.value = s; + fSub.dispatchEvent(new Event("change")); + if (d != null) { + fDet.value = d; + fDet.dispatchEvent(new Event("change")); + } + await wait(100); + return !!pick.querySelector('[data-pick="SP-000001"]'); + }; + const picker = { + same: await pickerWith(promote.구분, promote.상세구분), + other: await pickerWith(subs.find((s) => s.name !== promote.구분)?.name ?? "", null), + }; + pick.closest(".m01-logic__backdrop")?.remove(); + // ④ 새 사전조합 처음 값(POST 본문만 잡음) + items[1].click(); + await until(() => list("precombo")); + btn(document.querySelector(".m02-main__slot"), "생성").click(); + const box = await until(() => document.querySelector(".m02-modal")); + box.querySelector("input").value = "시험 51-12"; + btn(box, "생성").click(); + await until(() => posted); + btn(document.querySelector(".m02-modal"), "닫기")?.click(); + document.querySelector(".m02-modal")?.closest(".ui-modal__backdrop, .ui-modal")?.remove(); + const created = { + 구분: posted?.문서?.구분, + 상세구분: posted?.문서?.상세구분, + want: [subs[0].name, subs[0].details[0] ?? null], + }; + return JSON.stringify({ heads, listSP, promote, precombo, switched, back, picker, created }); + } finally { + window.fetch = real; + } +})(); From e66c92f56294da2c15c4baf23af81a001b9dccb6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:50:05 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore(sync):=20=EA=B3=B5=EC=9C=A0=20docs=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=8B=B4=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- docs/raw/사용자요구/2026-10-05_수정요구.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 14f1cf7f7..4f385fa04 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -112,3 +112,5 @@ 단가산출과 일위대가 사전조합에서 테이블의 양식도 M01에 공통테이블 양식이 있을것같고 버튼들도 M01에 템플릿이 있을거야. 확인해봐 그리고 사전조합의 경우에 검색은 별도의 창이 아니라 구조물 조합의 일위대가 테이블의 조건 추가와 같은 기능이어야. 일위대가 사전조합에서 나오는 모당창의 형식 뿐만아니라 내용도 구조물조합의 일위대가의 상세 화면에서 로직 추가와 같아야된다고 선택되는 항목도 + +어차피 신규로 키값을 만들지 않았음? 그럼 당연히 해당 연결정보와 구분, 상세구분을 만들어야하지 않음? From ce211f779bd872805f3c37a0e767639f67eaabfd Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:52:53 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix(M02):=20=EC=82=AC=EC=A0=84=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=20=EA=B5=AC=EB=B6=84=20=EB=B9=88=20=EA=B0=92=20=3D=20?= =?UTF-8?q?=E3=80=8C=E2=80=94=E3=80=8D=20=EC=84=A0=ED=83=9D=EC=A7=80=20?= =?UTF-8?q?=C2=B7=20=EC=97=B0=EA=B2=B0=20=3D=20{=EB=A1=9C=EC=A7=81,=20?= =?UTF-8?q?=EC=B6=9C=EC=B2=98}(51-12=20=EC=84=9C=EB=B2=84=20=EC=9D=B4?= =?UTF-8?q?=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 옛 사전조합(구분 빈 글)이 첫 구분으로 보이던 어긋남 없앰 - 화면 시나리오 51-12 [만들기] 단추 이름 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- .../M02_MasterTemplete_Labor_Detail.ts | 14 ++++++-------- resources/tester/ui_checks/51-12_서고_구분.js | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts index e38ba6874..31e8cd9b9 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Detail.ts @@ -478,15 +478,11 @@ export function mountLaborDetail( }; const libMeta = (): HTMLElement[] => { if (promote) { - const link = (meta.연결 ?? {}) as { 로직?: string; 키?: string; 출처?: string }; + const link = (meta.연결 ?? {}) as { 로직?: string; 출처?: string }; return [ readField(tc("Sub"), "구분", meta.구분 ?? ""), readField(tc("Detail"), "상세구분", meta.상세구분 ?? ""), - readField( - tc("Lib_Link"), - "연결", - [link.로직 ?? link.키, link.출처].filter(Boolean).join(" · "), - ), + readField(tc("Lib_Link"), "연결", [link.로직, link.출처].filter(Boolean).join(" · ")), ]; } const sub = createSelectField({ options: [], label: tc("Sub"), disabled: !edit }); @@ -494,9 +490,11 @@ export function mountLaborDetail( sub.root.dataset.laborField = "구분"; detail.root.dataset.laborField = "상세구분"; let subs: SubBrief[] = []; - // 목록에 없는 지금 값도 선택지로 둠 + // 목록에 없는 지금 값(빈 것 = 「—」 · 옛 문서)도 선택지로 둠 const opts = (names: string[], now: string | null | undefined) => - [...new Set([...(now ? [now] : []), ...names])].map((v) => ({ value: v, text: v })); + [...new Set([now ?? "", ...names])] + .filter((v, i) => v || i === 0) + .map((v) => ({ value: v, text: v || "—" })); const fill = (): void => { sub.setOptions( opts( diff --git a/resources/tester/ui_checks/51-12_서고_구분.js b/resources/tester/ui_checks/51-12_서고_구분.js index 87d16fb80..52e315697 100644 --- a/resources/tester/ui_checks/51-12_서고_구분.js +++ b/resources/tester/ui_checks/51-12_서고_구분.js @@ -126,7 +126,7 @@ btn(document.querySelector(".m02-main__slot"), "생성").click(); const box = await until(() => document.querySelector(".m02-modal")); box.querySelector("input").value = "시험 51-12"; - btn(box, "생성").click(); + btn(box, "만들기").click(); await until(() => posted); btn(document.querySelector(".m02-modal"), "닫기")?.click(); document.querySelector(".m02-modal")?.closest(".ui-modal__backdrop, .ui-modal")?.remove(); From 74af9f8d9941c6bb1b465b5dee0dc8d4cd4239ae Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:52:54 +0900 Subject: [PATCH 4/7] =?UTF-8?q?chore(sync):=20=EA=B3=B5=EC=9C=A0=20docs=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=8B=B4=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- docs/raw/사용자요구/2026-10-05_수정요구.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 4f385fa04..292d015d7 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -114,3 +114,5 @@ 일위대가 사전조합에서 나오는 모당창의 형식 뿐만아니라 내용도 구조물조합의 일위대가의 상세 화면에서 로직 추가와 같아야된다고 선택되는 항목도 어차피 신규로 키값을 만들지 않았음? 그럼 당연히 해당 연결정보와 구분, 상세구분을 만들어야하지 않음? + +다음으로 구조물 조합에서 일위대가의 상세페이지에서 테이블 행 선택에서 단가승격이야 단가산출과 같은 항목이라 문제가 없는데 일위대가 사전조합의 경우에는 전체를 다 표현하는데 문제가 있어. 일위조합인 경우에는 조합의 세부항목을 기준으로 노무비, 재료비, 경비를 값을 가져와서 사용하면 가능하지 않을까? From a3368f206dec5a0657408fb104299be8dc3fc8f6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:56:05 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore(plan):=2010-5=20=EC=84=9C=EA=B3=A0=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=20=C2=B7=20=EC=9D=BC=EC=9C=84=EC=A1=B0?= =?UTF-8?q?=ED=95=A9=20=ED=95=98=EC=9C=84=20=ED=96=89=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=20=EC=9B=90=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- docs/raw/사용자요구/2026-10-05_수정요구.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 14f1cf7f7..292d015d7 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -112,3 +112,7 @@ 단가산출과 일위대가 사전조합에서 테이블의 양식도 M01에 공통테이블 양식이 있을것같고 버튼들도 M01에 템플릿이 있을거야. 확인해봐 그리고 사전조합의 경우에 검색은 별도의 창이 아니라 구조물 조합의 일위대가 테이블의 조건 추가와 같은 기능이어야. 일위대가 사전조합에서 나오는 모당창의 형식 뿐만아니라 내용도 구조물조합의 일위대가의 상세 화면에서 로직 추가와 같아야된다고 선택되는 항목도 + +어차피 신규로 키값을 만들지 않았음? 그럼 당연히 해당 연결정보와 구분, 상세구분을 만들어야하지 않음? + +다음으로 구조물 조합에서 일위대가의 상세페이지에서 테이블 행 선택에서 단가승격이야 단가산출과 같은 항목이라 문제가 없는데 일위대가 사전조합의 경우에는 전체를 다 표현하는데 문제가 있어. 일위조합인 경우에는 조합의 세부항목을 기준으로 노무비, 재료비, 경비를 값을 가져와서 사용하면 가능하지 않을까? From a522c1979bb182b319b9cf88ff86e6c80ac6cab3 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:58:11 +0900 Subject: [PATCH 6/7] =?UTF-8?q?chore(M02):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=20=E2=80=94=20=EB=A7=88=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=20=EC=84=9C=EA=B3=A0=20=EC=82=AC=EC=A0=84=EC=A1=B0=ED=95=A9=20?= =?UTF-8?q?SC-000001(=EA=B5=AC=EB=B6=84=20=EA=B3=A0=EB=A6=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- .../master_template/precombo/SC-000001.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/resources/master_template/precombo/SC-000001.json b/resources/master_template/precombo/SC-000001.json index c24f868cc..cb35b6b86 100644 --- a/resources/master_template/precombo/SC-000001.json +++ b/resources/master_template/precombo/SC-000001.json @@ -3,6 +3,23 @@ "키": "SC-000001", "이름": "테스트 사전조합", "단위": null, - "담은로직": [], + "담은로직": [ + { + "로직": "GF000297", + "입력값": {}, + "수량": { + "손": 1 + }, + "메모": null + }, + { + "로직": "GF000289", + "입력값": {}, + "수량": { + "손": 1 + }, + "메모": null + } + ], "본뜬": null } From 524c89d85c7371eb724b10830a04b914f929e2e2 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 20:58:39 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore(plan):=2010-5=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EC=B9=B8=202=ED=96=89=20=C2=B7=20=EC=8A=B9=EA=B2=A9=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=C2=B7=20=EC=A1=B0=ED=95=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=9A=94=EA=B5=AC=20=EC=9B=90=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- docs/raw/사용자요구/2026-10-05_수정요구.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 292d015d7..bebbfa64f 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -116,3 +116,6 @@ 어차피 신규로 키값을 만들지 않았음? 그럼 당연히 해당 연결정보와 구분, 상세구분을 만들어야하지 않음? 다음으로 구조물 조합에서 일위대가의 상세페이지에서 테이블 행 선택에서 단가승격이야 단가산출과 같은 항목이라 문제가 없는데 일위대가 사전조합의 경우에는 전체를 다 표현하는데 문제가 있어. 일위조합인 경우에는 조합의 세부항목을 기준으로 노무비, 재료비, 경비를 값을 가져와서 사용하면 가능하지 않을까? + +추가로 구조물 조합의 일위대가 상세페이지의 로직 테이블의 순서 열에서 존재하는 버튼을 2행 구조로 변경 +단가 승격에도 옵션 버튼이있어야할것같고. 일위조합의 경우에는 바로 옵션으로 지정할수 없으니 수정버튼(모달)도 잇어할것 같은데?