diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab.ts b/M01_MasterData/M01_MasterData_UI_LogicLab.ts index 236bc196..77ecbd8b 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab.ts @@ -34,6 +34,8 @@ import { buildList, logicId, type ListItem, type ListMark } from "./M01_MasterDa import type { SideHandle } from "./M01_MasterData_UI_Side"; import { tx } from "./M01_MasterData_UI_Logic_Text"; import { buildLabDetail } from "./M01_MasterData_UI_LogicLab_Detail"; +import { buildNewLogic } from "./M01_MasterData_UI_LogicLab_New"; +import { tn } from "./M01_MasterData_UI_LogicLab_New_Text"; import "./M01_MasterData_UI_Logic_Style.css"; /** 저장 안 한 로직 하나 — origKey null = 새 로직 · row null = 지움 */ @@ -80,10 +82,12 @@ export async function mountM01LogicLab( ): Promise { let drafts = loadDrafts(); let opened: Opened | null = null; + let creating = false; // 「식으로 새로 만들기」 화면이 열려 있음 let calcInputs = ""; const editor = el("div", { className: "m01-logic__editor" }); const errors = el("div", { className: "m01-logic__reasons", attrs: { hidden: "" } }); const calc = el("div", { className: "m01lab__calc" }); + const newHost = el("div", { className: "m01lab__new", attrs: { hidden: "" } }); const filterHost = el("div"); const waiting = el("p", { className: "m01-logic__muted", text: tx("Loading") }); let loaded = false; // 첫 목록이 오기 전에는 「0 줄」 빈 표를 보이지 않음 @@ -165,10 +169,12 @@ export async function mountM01LogicLab( const drawEditor = (): void => { // 고른 로직이 없으면 목록 · 있으면 호표 표 + 시험 계산 - const listing = !opened; + const listing = !opened && !creating; list.root.hidden = !listing || !loaded; waiting.hidden = loaded; - editor.hidden = back.hidden = listing; + editor.hidden = !opened; + back.hidden = listing; + newHost.hidden = !creating; if (!opened) return; if (!opened.row) { editor.replaceChildren( @@ -192,6 +198,7 @@ export async function mountM01LogicLab( const show = (next: Opened | null): void => { opened = next; + creating = false; errors.hidden = true; drawEditor(); drawCalc(); @@ -251,7 +258,7 @@ export async function mountM01LogicLab( restore: true, onPick: (sub, detail) => { list.setPick(sub, detail); - if (opened) show(null); + if (opened || creating) show(null); }, }), ); @@ -335,6 +342,29 @@ export async function mountM01LogicLab( } }; + /** 식으로 새로 만들기 — 저장하면 목록을 다시 받아 그 로직의 상세 화면을 엶 */ + const startNew = (): void => { + opened = null; + creating = true; + newHost.replaceChildren( + buildNewLogic({ + onSaved: (key) => { + void (async () => { + try { + await reload(); + await openKeyOn(key); + } catch (error) { + showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error"); + } + })(); + }, + onClose: () => show(null), + }), + ); + drawEditor(); + drawCalc(); + }; + const saveButton = createButton({ label: tx("Bar_Save"), onClick: () => void onSave() }); const discardButton = createButton({ label: tx("Bar_Discard"), @@ -347,6 +377,7 @@ export async function mountM01LogicLab( back, el("h2", { text: tx("Title") }), pending, + createButton({ label: tn("Open"), variant: "filled", onClick: startNew }), createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }), discardButton, saveButton, @@ -358,7 +389,7 @@ export async function mountM01LogicLab( children: [ el("main", { className: "m01-logic__main", - children: [bar, errors, waiting, list.root, editor], + children: [bar, errors, waiting, list.root, editor, newHost], }), ], }), diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_New.css b/M01_MasterData/M01_MasterData_UI_LogicLab_New.css new file mode 100644 index 00000000..bd23745e --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_New.css @@ -0,0 +1,105 @@ +/* M01 로직 개선 시험 — 식으로 새 로직 만들기 (좁은 화면에서도 한 칸으로 쌓임) */ +.m01lab-new { + display: flex; + flex-direction: column; + gap: var(--spacing-12); + min-width: 0; +} + +.m01lab-new__step { + display: flex; + flex-direction: column; + gap: var(--spacing-8); + min-width: 0; +} + +.m01lab-new__text { + width: 100%; + min-height: 140px; + padding: var(--spacing-8); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + background: var(--color-bg); + color: var(--color-text); + font-family: ui-monospace, Consolas, monospace; + font-size: var(--text-body); + resize: vertical; +} + +.m01lab-new__row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--spacing-8); +} + +.m01lab-new__vars { + display: flex; + flex-direction: column; + gap: var(--spacing-8); +} + +.m01lab-new__var { + padding: var(--spacing-8); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); +} + +.m01lab-new__var-head { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--spacing-8); +} + +.m01lab-new__var-body { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: var(--spacing-8); + margin-top: var(--spacing-8); +} + +.m01lab-new__wide { + flex: 1 1 220px; +} + +.m01lab-new__picked { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--spacing-8); + min-width: 0; +} + +.m01lab-new__src { + display: flex; + gap: var(--spacing-8); + padding: var(--spacing-4) var(--spacing-8); + border-radius: var(--radius-lg); + background: var(--color-paper); + overflow-x: auto; +} + +.m01lab-new__no { + flex: 0 0 auto; + color: var(--color-text-secondary); +} + +.m01lab-new__src mark { + background: var(--color-danger); + color: var(--color-bg); + border-radius: 2px; +} + +.m01lab-new__bad { + color: var(--color-danger); +} + +.m01lab-new__soft { + color: var(--color-text-secondary); +} + +.m01lab-new__calc { + min-width: 0; +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_New.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_New.ts new file mode 100644 index 00000000..eb828837 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_New.ts @@ -0,0 +1,272 @@ +/* ============================================================================= + * M01_MasterData_UI_LogicLab_New.ts + * 「로직 개선 시험」 — 식으로 새 로직 만들기 (PLAN 3-3 · 계약 `_화면_계약.md` 7장) + * ① 식을 줄로 적음 ② [분석] → 변수 표에서 변수마다 무엇인지·단위·비목을 고름 + * ③ 이름·결과 단위 ④ 시험 계산으로 값을 보고 저장(자체 로직 · 키 GX) + * 검사에 걸린 것은 그 줄·자리를 짚어 쉬운 말로 보임 — 서버 글 그대로(`문제`) + * ========================================================================== */ + +import { + createButton, + createInputField, + el, + hideLoadingOverlay, + showLoadingOverlay, + showToast, +} from "@ui/ui_template_elements"; +import { + ApiError, + draftLogic, + type DraftAnswer, + type DraftDecided, + type DraftHead, + type DraftProblem, + type DraftVar, +} from "./M01_MasterData_UI_Logic_Api"; +import { buildCalc } from "./M01_MasterData_UI_Logic_Calc"; +import { tn } from "./M01_MasterData_UI_LogicLab_New_Text"; +import { varRow, type VarRow } from "./M01_MasterData_UI_LogicLab_New_Var"; +import "./M01_MasterData_UI_LogicLab_New.css"; + +/** 보기 글 — 넓이 → 품 → 인부 한 줄 + 잡재료비 덧줄 */ +const SAMPLE = `# 보기 — 분석을 누른 뒤 변수마다 무엇인지 골라 주세요 +넓이 = 길이 * 폭 +인부 = 넓이 * 품 +잡재료비 = 노무비 * 0.05 +공사비 = 인부 * 보통인부 + 잡재료비 +`; + +export interface NewOptions { + /** 저장 끝 — 새 자체 로직의 키 */ + onSaved: (key: string) => void; + onClose: () => void; +} + +export function buildNewLogic(opts: NewOptions): HTMLElement { + const decided: Record = {}; + const rows = new Map(); + const values: Record = {}; + let analyzed = ""; // 분석한 식 — 그 뒤 글이 바뀌면 다시 분석하라고 알림 + let answer: DraftAnswer | null = null; + let calcSig = ""; + let seq = 0; + + const text = el("textarea", { + className: "m01lab-new__text", + attrs: { rows: "7", spellcheck: "false", "aria-label": tn("Step1") }, + }); + text.value = SAMPLE; + const stale = el("p", { + className: "m01-logic__muted", + text: tn("Stale"), + attrs: { hidden: "" }, + }); + const varsHost = el("div", { className: "m01lab-new__vars" }); + const problemHost = el("div", { className: "m01lab-new__problems" }); + const calcHost = el("div", { className: "m01lab__calc m01lab-new__calc" }); + const nameBox = createInputField({ + type: "text", + label: tn("Name"), + onInput: () => onEdit(), + }); + nameBox.input.setAttribute("data-key", "name"); + const unitBox = createInputField({ + type: "text", + label: tn("ResultUnit"), + placeholder: "원/㎡", + onInput: () => onEdit(), + }); + unitBox.input.setAttribute("data-key", "unit"); + const ownerBox = createInputField({ type: "text", label: tn("Owner") }); + const state = el("p", { className: "m01-logic__muted" }); + const save = createButton({ label: tn("Save"), onClick: () => void doSave() }); + save.setAttribute("data-act", "save"); + save.disabled = true; + + const head = (): DraftHead | undefined => { + const 이름 = nameBox.input.value.trim(); + const 결과단위 = unitBox.input.value.trim(); + return 이름 && 결과단위 ? { 이름, 결과단위, 출처: "자체" } : undefined; + }; + + /** 문제가 걸린 줄을 그대로 보이고 자리 글자에 표시 · 아래에 쉬운 말 목록 */ + const drawProblems = (problems: DraftProblem[]): void => { + if (!analyzed) return problemHost.replaceChildren(); + const lines = analyzed.split("\n"); + const flagged = new Map(); + for (const p of problems) { + if (p.줄) flagged.set(p.줄, [...(flagged.get(p.줄) ?? []), p]); + } + const shown = [...flagged.keys()] + .sort((a, b) => a - b) + .filter((n) => n <= lines.length) + .map((n) => { + const raw = lines[n - 1]; + const at = flagged.get(n)?.find((p) => p.자리 !== null)?.자리 ?? -1; + return el("div", { + className: "m01lab-new__src", + children: [ + el("span", { className: "m01lab-new__no", text: String(n) }), + at >= 0 + ? el("code", { + children: [ + raw.slice(0, at), + el("mark", { text: raw.slice(at, at + 1) }), + raw.slice(at + 1), + ], + }) + : el("code", { text: raw }), + ], + }); + }); + const items = problems.map((p) => + el("li", { + className: p.갈래 === "덜정함" ? "m01lab-new__soft" : "m01lab-new__bad", + attrs: { "data-kind": p.갈래 }, + text: `${p.줄 ? `${tn("Line", { n: p.줄 })} · ` : ""}${p.말}`, + }), + ); + problemHost.replaceChildren( + el("h4", { text: tn("Problems") }), + ...(items.length + ? [...shown, el("ul", { children: items })] + : [el("p", { className: "m01-logic__muted", text: tn("AllGood") })]), + ); + }; + + const drawCalc = (): void => { + const row = answer?.logic ?? null; + save.disabled = !row || (answer?.문제.length ?? 1) > 0; + state.textContent = !answer + ? "" + : answer.문제.some((p) => p.갈래 !== "덜정함") + ? tn("SaveBlocked") + : !head() + ? tn("NeedHead") + : answer.다됨 + ? "" + : tn("Undecided"); + if (!row) { + calcSig = ""; + calcHost.replaceChildren(); + return; + } + const sig = JSON.stringify(row.입력 ?? []); + if (sig === calcSig) return; // 입력 칸이 그대로면 넣은 값·결과를 안 지우려고 다시 안 그림 + calcSig = sig; + buildCalc(calcHost, { + savedKey: null, + row: { ...row, 키: row.키 ?? "" }, // 아직 번호 없는 새 초안 — 키 칸은 비워 보냄 + dirty: () => true, + values, + onLines: () => undefined, + }); + }; + + const drawVars = (vars: DraftVar[]): void => { + rows.clear(); + const shown = vars.filter((v) => v.갈래 !== "비목합"); + for (const name of Object.keys(decided)) { + if (!shown.some((v) => v.이름 === name)) delete decided[name]; + } + const result = answer?.결과 ?? ""; + const order = [ + ...shown.filter((v) => v.갈래 === "미정"), + ...shown.filter((v) => v.갈래 !== "미정"), + ]; + for (const v of order) rows.set(v.이름, varRow(v, v.이름 === result, { decided, onEdit })); + varsHost.replaceChildren(...[...rows.values()].map((r) => r.root)); + }; + + const evaluate = async (rebuild: boolean): Promise => { + if (!analyzed) return; + const mine = ++seq; + try { + const got = await draftLogic({ text: analyzed, decided, head: head() }); + if (mine !== seq) return; // 최신 응답만 + answer = got; + if (rebuild) drawVars(got.변수); + for (const v of got.변수) rows.get(v.이름)?.update(v); + drawProblems(got.문제); + drawCalc(); + } catch (error) { + showToast(error instanceof Error ? error.message : tn("Failed"), "error"); + } + }; + let timer = 0; + function onEdit(): void { + window.clearTimeout(timer); + timer = window.setTimeout(() => void evaluate(false), 250); + } + + const analyzeButton = createButton({ + label: tn("Analyze"), + onClick: () => { + analyzed = text.value; + stale.hidden = true; + void evaluate(true); + }, + }); + analyzeButton.setAttribute("data-act", "analyze"); + text.addEventListener("input", () => (stale.hidden = !analyzed || text.value === analyzed)); + + async function doSave(): Promise { + const h = head(); + if (!h) return void showToast(tn("NeedHead"), "info"); + save.disabled = true; + showLoadingOverlay(); // 서버가 파일을 검사하며 저장하느라 몇 초 걸림 + try { + const owner = ownerBox.input.value.trim(); + const done = await draftLogic({ + text: analyzed, + decided, + head: h, + save: true, + ...(owner ? { owner } : {}), + }); + showToast(tn("Saved", { v: done.key ?? "" }), "success"); + if (done.key) opts.onSaved(done.key); + } catch (error) { + const detail = error instanceof ApiError ? (error.detail as { 문제?: DraftProblem[] }) : null; + if (detail?.문제) drawProblems(detail.문제); + else showToast(error instanceof Error ? error.message : tn("Failed"), "error"); + save.disabled = false; + } finally { + hideLoadingOverlay(); + } + } + + const section = (title: string, ...children: HTMLElement[]): HTMLElement => + el("section", { + className: "m01lab__box m01lab-new__step", + children: [el("h3", { text: title }), ...children], + }); + + return el("div", { + className: "m01lab-new", + children: [ + el("div", { + className: "m01-logic__section-head", + children: [ + el("h2", { text: tn("Title") }), + createButton({ label: tn("Close"), variant: "ghost", onClick: opts.onClose }), + ], + }), + section( + tn("Step1"), + el("p", { className: "m01-logic__muted", text: tn("Guide") }), + text, + el("div", { className: "m01lab-new__row", children: [analyzeButton, stale] }), + ), + section(tn("Step2"), varsHost, problemHost), + section( + tn("Step3"), + el("div", { + className: "m01lab-new__row", + children: [nameBox.root, unitBox.root, ownerBox.root], + }), + ), + section(tn("Step4"), calcHost, state, save), + ], + }); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_New_Find.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Find.ts new file mode 100644 index 00000000..08761a62 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Find.ts @@ -0,0 +1,218 @@ +/* ============================================================================= + * M01_MasterData_UI_LogicLab_New_Find.ts + * 새 로직 만들기의 고르기 창 둘 + * ① 마스터 요소 — 종류(인력·재료·기계) → 구분 → 상세구분 → 이름 찾기 → 한 줄 고르기 + * ② 표 찾기 — 표(소요량·계수)와 값 칸을 고른 뒤 표의 조건 칸마다 식을 적음 + * ========================================================================== */ + +import { + createButton, + createInputField, + createSelectField, + el, + showToast, +} from "@ui/ui_template_elements"; +import { + fetchFiles, + fetchRows, + fetchSubsAll, + type FileInfo, + type SubInfo, +} from "./M01_MasterData_Api_Fetch"; +import type { DraftFind } from "./M01_MasterData_UI_Logic_Api"; +import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; +import { loadTable } from "./M01_MasterData_UI_Logic_Note"; +import { openPicker } from "./M01_MasterData_UI_Logic_Pick"; +import { tn } from "./M01_MasterData_UI_LogicLab_New_Text"; + +export interface PickedElement { + ref: string; + /** 이름 + 규격 */ + name: string; + /** 수량에 쓰는 단위(「원/일」 → 「일」) */ + unit: string; + group: string; +} + +const GROUPS = ["인력", "재료", "기계"]; +const SHOWN = 50; + +/** 창 뼈대 — 제목 · 닫기 · 몸통 */ +function shell(title: string, body: HTMLElement[]): { close: () => void } { + const close = (): void => backdrop.remove(); + const dialog = el("div", { + className: "m01-logic__pick", + attrs: { role: "dialog", "aria-label": title }, + children: [ + el("div", { + className: "m01-logic__section-head", + children: [ + el("h3", { text: title }), + createButton({ label: tn("Close"), variant: "ghost", onClick: close }), + ], + }), + ...body, + ], + }); + const backdrop = el("div", { className: "m01-logic__backdrop", children: [dialog] }); + backdrop.addEventListener("click", (ev) => ev.target === backdrop && close()); + backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close()); + document.body.append(backdrop); + return { close }; +} + +/** 그룹의 파일 — 재료는 자재품목(구분·상세구분이 있는 것) */ +async function fileOf(group: string): Promise { + const files = await fetchFiles(group); + return group === "재료" ? (files.find((f) => f.file.includes("자재품목")) ?? files[0]) : files[0]; +} + +const options = (values: string[]): { value: string; text: string }[] => [ + { value: "", text: tn("All") }, + ...values.map((v) => ({ value: v, text: v })), +]; + +export function openElementPick(done: (picked: PickedElement) => void): void { + let file: FileInfo | undefined; + let subs: SubInfo[] = []; + const groupBox = createSelectField({ + options: GROUPS.map((g) => ({ value: g, text: g })), + value: GROUPS[0], + compact: true, + label: tn("Group"), + }); + const subBox = createSelectField({ + options: options([]), + value: "", + compact: true, + label: tn("Sub"), + }); + const detailBox = createSelectField({ + options: options([]), + value: "", + compact: true, + label: tn("Detail"), + }); + const searchField = createInputField({ type: "search", placeholder: tn("Search") }); + const count = el("span", { className: "m01-logic__muted" }); + const list = el("div", { className: "m01-logic__pick-list" }); + const win = shell(tn("K_master"), [ + el("div", { + className: "m01-logic__pick-bar", + children: [groupBox.root, subBox.root, detailBox.root, searchField.root, count], + }), + list, + ]); + + let seq = 0; + const load = async (): Promise => { + if (!file) return; + const mine = ++seq; + try { + const got = await fetchRows( + file.file, + 1, + SHOWN, + searchField.input.value.trim(), + false, + subBox.select.value, + detailBox.select.value, + ); + if (mine !== seq) return; + count.textContent = tn("Count", { n: got.total, m: Math.min(got.total, SHOWN) }); + list.replaceChildren( + ...got.rows.map((row) => { + const name = `${row["이름"] ?? ""} ${row["규격"] ?? ""}`.trim(); + const unitText = String(row["단위"] ?? ""); + const unit = groupBox.select.value === "인력" ? "인" : (unitText.split("/").pop() ?? ""); + const button = el("button", { + className: "m01-logic__pick-row m01-logic__pick-row--button", + attrs: { type: "button", "data-ref": String(row["키"]) }, + children: [ + el("div", { + className: "m01-logic__stack", + children: [ + el("strong", { text: name }), + el("span", { className: "m01-logic__muted", text: String(row["키"]) }), + ], + }), + el("span", { + className: "m01-logic__money", + text: `${formatNumber(row["값"])} ${unitText}`.trim(), + }), + ], + }); + button.addEventListener("click", () => { + win.close(); + done({ ref: String(row["키"]), name, unit, group: groupBox.select.value }); + }); + return button; + }), + ); + } catch (error) { + showToast(error instanceof Error ? error.message : tn("Failed"), "error"); + } + }; + + const changeGroup = async (): Promise => { + file = await fileOf(groupBox.select.value); + subs = file ? (await fetchSubsAll({ file: file.file })).subs : []; + subBox.setOptions(options(subs.map((s) => s.name)), ""); + detailBox.setOptions(options([]), ""); + detailBox.select.disabled = true; + await load(); + }; + groupBox.select.addEventListener("change", () => void changeGroup()); + subBox.select.addEventListener("change", () => { + const details = subs.find((s) => s.name === subBox.select.value)?.details ?? []; + detailBox.setOptions(options(details), ""); + detailBox.select.disabled = details.length === 0; + void load(); + }); + detailBox.select.addEventListener("change", () => void load()); + let timer = 0; + searchField.input.addEventListener("input", () => { + window.clearTimeout(timer); + timer = window.setTimeout(() => void load(), 250); + }); + void changeGroup(); +} + +/** 표 찾기 — 표·값 칸을 고른 뒤 조건 칸마다 식을 받음 · 표의 조건 칸이 없으면 바로 끝 */ +export function openTablePick(done: (find: DraftFind, label: string) => void): void { + openPicker("table", "소요량", (item, column) => { + void (async () => { + const table = await loadTable(item.ref); + const conds = Object.keys(table?.조건 ?? {}); + const label = `${item.이름 ?? item.ref} · ${item.ref} → ${column ?? ""}`; + const find = { 표: item.ref, 값칸: column ?? "", 조건: {} as Record }; + if (!conds.length) { + done(find, label); + return; + } + const inputs = conds.map((c) => { + const box = createInputField({ type: "text", value: c }); + box.input.setAttribute("data-cond", c); + return { c, box }; + }); + const win = shell(tn("TableTitle"), [ + el("p", { className: "m01-logic__muted", text: label }), + el("p", { text: tn("Cond") }), + ...inputs.map(({ c, box }) => + el("label", { + className: "m01-logic__field", + children: [el("span", { text: c }), box.root], + }), + ), + createButton({ + label: tn("Ok"), + onClick: () => { + for (const { c, box } of inputs) find.조건[c] = box.input.value.trim() || c; + win.close(); + done(find, label); + }, + }), + ]); + })(); + }); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_New_Text.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Text.ts new file mode 100644 index 00000000..272d234f --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Text.ts @@ -0,0 +1,71 @@ +/* ============================================================================= + * M01_MasterData_UI_LogicLab_New_Text.ts + * 「식으로 새 로직 만들기」 글자 — [한국어, 영어] + * ========================================================================== */ + +import { currentLanguageIndex } from "@ui/ui_template_locale"; + +const TEXT = { + Open: ["식으로 새로 만들기", "New from formula"], + Title: ["식으로 새 로직 만들기", "New logic from a formula"], + Close: ["닫기", "Close"], + Step1: ["① 식 적기", "① Write the formula"], + Guide: [ + "한 줄에 「이름 = 식」 · 마지막 줄 왼쪽이 이 로직의 결과 · 「#」 로 시작한 줄은 건너뜀", + "One line each as “name = formula” · the last line's left side is the result · lines starting with # are skipped", + ], + Analyze: ["분석", "Analyze"], + Stale: ["식이 바뀌었음 — 다시 [분석] 을 눌러 주세요", "Formula changed — press Analyze again"], + Step2: ["② 변수 정하기", "② Decide each variable"], + Step3: ["③ 이름·단위", "③ Name and unit"], + Step4: ["④ 시험 계산 · 저장", "④ Trial calculation and save"], + Problems: ["고칠 곳", "To fix"], + Line: ["{n}번 줄", "Line {n}"], + AllGood: ["검사에 걸린 것 없음", "No problems found"], + Result: ["결과", "Result"], + Undecided: ["아직 정하지 않은 칸이 있음", "Some cells are not decided yet"], + Kind: ["무엇인가", "What is it"], + K_master: ["마스터 요소", "Master element"], + K_input: ["설계 입력", "Design input"], + K_fixed: ["고정값", "Fixed value"], + K_table: ["표 찾기", "Table lookup"], + Pick: ["고르기", "Pick"], + Repick: ["다시 고르기", "Pick again"], + NotPicked: ["아직 안 골랐음", "Not picked"], + Unit: ["단위", "Unit"], + Cost: ["비목", "Cost item"], + CostNone: ["넣지 않음", "None"], + CostExtra: ["덧줄 비목", "Extra-line cost item"], + Value: ["값", "Value"], + Note: ["설명", "Note"], + Derived: ["식으로 정해짐", "Set by the formula"], + SumName: ["비목 합 — 덧줄에서만 쓰는 이름", "Cost total — only for extra lines"], + Name: ["로직 이름", "Logic name"], + ResultUnit: ["결과 단위", "Result unit"], + Owner: ["소유", "Owner"], + Save: ["저장", "Save"], + Saved: ["저장했음 — 「{v}」", "Saved — “{v}”"], + SaveBlocked: ["위 고칠 곳을 먼저 고치세요", "Fix the problems above first"], + NeedHead: ["이름과 결과 단위를 적어야 시험 계산·저장이 됨", "Enter the name and result unit"], + Group: ["종류", "Kind"], + Sub: ["구분", "Group"], + Detail: ["상세구분", "Detail"], + All: ["(전체)", "(All)"], + Search: ["이름 찾기", "Search name"], + Count: ["{n} 개 중 앞 {m} 개", "first {m} of {n}"], + TableTitle: ["표 찾기 — 표와 조건", "Table lookup — table and conditions"], + TablePick: ["표 고르기", "Pick a table"], + TableCol: ["값 칸", "Value column"], + Cond: ["조건 — 표의 칸마다 식(설계 값 이름)", "Conditions — a formula per table column"], + Ok: ["확인", "OK"], + Cancel: ["취소", "Cancel"], + Failed: ["못 함", "Failed"], +} as const satisfies Record; + +export type NewTextKey = keyof typeof TEXT; + +export function tn(key: NewTextKey, fill: Record = {}): string { + const entry = TEXT[key]; + const text: string = entry[currentLanguageIndex as 0 | 1] ?? entry[0]; + return text.replace(/\{(\w+)\}/g, (m, k: string) => (k in fill ? String(fill[k]) : m)); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_New_Var.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Var.ts new file mode 100644 index 00000000..7cfb70d1 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_New_Var.ts @@ -0,0 +1,178 @@ +/* ============================================================================= + * M01_MasterData_UI_LogicLab_New_Var.ts + * 변수 한 칸 — 무엇인지(마스터 요소 / 설계 입력 / 고정값 / 표 찾기) · 단위 · 비목을 고름 + * 고른 것은 `decided[이름]` 에 쌓음(서버 계약 `_화면_계약.md` 7장의 `decided` 그대로) + * ========================================================================== */ + +import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements"; +import type { DraftDecided, DraftVar } from "./M01_MasterData_UI_Logic_Api"; +import { openElementPick, openTablePick } from "./M01_MasterData_UI_LogicLab_New_Find"; +import { tn } from "./M01_MasterData_UI_LogicLab_New_Text"; + +export interface VarCtx { + decided: Record; + /** 고른 것이 바뀜 — 화면이 검사를 다시 부름 */ + onEdit: () => void; +} +export interface VarRow { + root: HTMLElement; + /** 서버가 다시 준 변수(식으로 정해진 단위 등)를 반영 */ + update: (v: DraftVar) => void; +} + +const KINDS: [string, () => string][] = [ + ["마스터요소", () => tn("K_master")], + ["설계입력", () => tn("K_input")], + ["고정값", () => tn("K_fixed")], + ["표찾기", () => tn("K_table")], +]; +const COSTS = ["노무비", "재료비", "경비"]; +/** 요소 종류 → 비목 첫 제안 */ +const COST_OF: Record = { 인력: "노무비", 재료: "재료비", 기계: "경비" }; + +const costOptions = (): { value: string; text: string }[] => [ + { value: "", text: tn("CostNone") }, + ...COSTS.map((c) => ({ value: c, text: c })), +]; + +const field = (label: string, control: HTMLElement): HTMLElement => + el("label", { + className: "m01-logic__field", + children: [el("span", { text: label }), control], + }); + +export function varRow(v: DraftVar, isResult: boolean, ctx: VarCtx): VarRow { + const name = v.이름; + const info = el("span", { className: "m01-logic__muted m01lab-new__derived" }); + const body = el("div", { className: "m01lab-new__var-body" }); + const root = el("div", { + className: "m01lab-new__var", + attrs: { "data-var": name }, + children: [ + el("div", { + className: "m01lab-new__var-head", + children: [el("strong", { text: name }), info], + }), + body, + ], + }); + const now = (): DraftDecided => (ctx.decided[name] ??= {}); + const set = (patch: DraftDecided): void => { + ctx.decided[name] = { ...now(), ...patch }; + for (const [k, val] of Object.entries(ctx.decided[name])) { + if (val === "" || val === undefined) delete (ctx.decided[name] as Record)[k]; + } + ctx.onEdit(); + }; + const text = (label: string, key: "단위" | "값" | "설명", wide = false): HTMLElement => { + const box = createInputField({ + type: "text", + value: now()[key] ?? "", + onInput: (val) => set({ [key]: val.trim() }), + }); + box.input.setAttribute("data-key", key); + const cell = field(label, box.root); + if (wide) cell.classList.add("m01lab-new__wide"); + return cell; + }; + + let picked = ""; // 마스터 요소·표 찾기에서 고른 것의 글 + const draw = (): void => { + const kind = now().무엇 ?? ""; + if (v.갈래 === "비목합") { + body.replaceChildren(el("span", { className: "m01-logic__muted", text: tn("SumName") })); + return; + } + if (v.갈래 === "정의됨") { + if (isResult) return body.replaceChildren(); + const cost = createSelectField({ + options: costOptions(), + value: now().비목 ?? "", + compact: true, + label: tn("CostExtra"), + onChange: (val) => set({ 비목: val }), + }); + body.replaceChildren(cost.root); + return; + } + const kindBox = createSelectField({ + options: [ + { value: "", text: "—" }, + ...KINDS.map(([value, label]) => ({ value, text: label() })), + ], + value: kind, + compact: true, + label: tn("Kind"), + onChange: (val) => { + ctx.decided[name] = val ? { 무엇: val, ...(now().단위 ? { 단위: now().단위 } : {}) } : {}; + picked = ""; + draw(); + ctx.onEdit(); + }, + }); + const parts: HTMLElement[] = [kindBox.root]; + if (kind === "마스터요소") { + const cost = createSelectField({ + options: costOptions(), + value: now().비목 ?? "", + compact: true, + label: tn("Cost"), + onChange: (val) => set({ 비목: val }), + }); + const choose = createButton({ + label: now().요소 ? tn("Repick") : tn("Pick"), + variant: "ghost", + onClick: () => + openElementPick((p) => { + picked = `${p.name} · ${p.ref}`; + set({ 요소: p.ref, 단위: p.unit, 비목: COST_OF[p.group] ?? now().비목 }); + draw(); + }), + }); + choose.setAttribute("data-act", "pick-element"); + parts.push( + el("div", { + className: "m01lab-new__picked", + children: [choose, el("span", { text: picked || now().요소 || tn("NotPicked") })], + }), + text(tn("Unit"), "단위"), + cost.root, + ); + } else if (kind === "설계입력") { + parts.push(text(tn("Unit"), "단위"), text(tn("Note"), "설명", true)); + } else if (kind === "고정값") { + parts.push(text(tn("Value"), "값"), text(tn("Unit"), "단위")); + } else if (kind === "표찾기") { + const choose = createButton({ + label: now().찾기 ? tn("Repick") : tn("TablePick"), + variant: "ghost", + onClick: () => + openTablePick((find, label) => { + picked = label; + set({ 찾기: find }); + draw(); + }), + }); + choose.setAttribute("data-act", "pick-table"); + parts.push( + el("div", { + className: "m01lab-new__picked", + children: [choose, el("span", { text: picked || tn("NotPicked") })], + }), + text(tn("Unit"), "단위"), + ); + } + body.replaceChildren(...parts); + }; + draw(); + + const update = (next: DraftVar): void => { + v = next; + info.textContent = + next.갈래 === "정의됨" + ? `${tn("Derived")}${next.단위 ? ` · ${next.단위}` : ""}` + : (next.단위 ?? ""); + }; + update(v); + return { root, update }; +} diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts index f2c06d1e..4a8ce52b 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts @@ -214,3 +214,66 @@ export const copyLogic = ( key: string, ): Promise<{ file: string; version: string; key: string; logic: LogicRow }> => request("/logic/copy", { key }); + +/* ── 식으로 새 로직 만들기(계약 7장) ── */ +export interface DraftFind { + 표: string; + 값칸: string; + 조건: Record; +} +/** 변수마다 정한 것 */ +export interface DraftDecided { + 무엇?: string; + 단위?: string; + 비목?: string; + 요소?: string; + 찾기?: DraftFind; + 값?: string; + 설명?: string; +} +export interface DraftVar { + 이름: string; + 처음: number; + 갈래: "정의됨" | "미정" | "비목합"; + 정의줄: number | null; + 쓰임: number[]; + 무엇: string | null; + 단위: string | null; + 비목: string | null; + 정할것: string[]; +} +export interface DraftProblem { + 줄: number | null; + 자리: number | null; + 갈래: string; + 말: string; +} +export interface DraftAnswer { + 줄: { 줄: number; 이름: string; 식: string; 앞: number }[]; + 변수: DraftVar[]; + 결과: string; + 문제: DraftProblem[]; + 다됨: boolean; + logic: LogicRow | null; + /** 저장했을 때만 */ + file?: string; + version?: string; + key?: string; +} +export interface DraftHead { + 이름: string; + 결과단위: string; + 원문번호?: string; + 출처?: string; + 비고?: string; + 끝수?: string; +} + +/** 검사·미리보기 · `save` 면 자체 로직(키 `GX`)으로 저장 — 문제가 있으면 422 */ +export const draftLogic = (body: { + text: string; + decided: Record; + head?: DraftHead; + save?: boolean; + owner?: string; +}): Promise => request("/logic/draft", body); diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts index e01d490f..9582a37b 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts @@ -19,7 +19,8 @@ import { tx } from "./M01_MasterData_UI_Logic_Text"; export interface CalcContext { /** 저장된 키 — 새 로직은 null */ savedKey: string | null; - file: string; + /** 저장 전 새 초안이면 비움 — 서버가 임시 자리에 얹어 셈 */ + file?: string; row: LogicRow; dirty: () => boolean; /** 로직마다 넣은 값 — 다시 그려도 남음 */ diff --git a/resources/master_data/ref/_남은_옛재료줄.md b/resources/master_data/ref/_남은_옛재료줄.md new file mode 100644 index 00000000..52f3d590 --- /dev/null +++ b/resources/master_data/ref/_남은_옛재료줄.md @@ -0,0 +1,45 @@ +# 남은 옛 방식 재료 줄 — 고르기 조건으로 못 바꾼 것 (2026-09-21) + +옛 방식 = 로직 호표의 「요소」 가 품셈재료(`MP……`)·자재품목(`MT……`) 줄을 바로 가리키는 것. +이번에 **307줄 → 178줄**(129줄을 고르기 조건으로 바꿈 · 금액 바뀐 로직 0). + +## 바꾼 것 129줄 (참고) + +| 갈래 | 줄 | 바꾼 모양 | +|---|---|---| +| `MT……` 를 바로 가리킴(17 재료) | 112 | `{구분, 상세구분, 규격, 대표=그 MT 줄}` | +| 연결이 `MT……` 키 하나인 품셈재료(9 재료) | 17 | 같음(연결이 가리키던 줄이 대표) | + +대표 줄이 옛 요소와 같은 줄이라 **시험 계산 금액이 그대로** — 화면에서 같은 구분·상세구분 안 후보를 볼 수 있게만 열림. + +## 못 바꾼 것 178줄 — 까닭별 + +### ① 자재품목에 짝이 없는 품셈재료 — 139줄 · 재료 111 · 로직 55 (가장 큼) + +품셈재료 줄에 「연결」 도 「고르기」 도 없음 = 자재품목(조달청·물가지)에 그 물건이 없음. +바꿀 근거(구분·상세구분)가 없어 조건을 못 만듦 — **재료를 먼저 채워야 바뀜**(목록 `ref/_없는_재료.md` 111줄과 같은 것들). + +많이 쓰이는 것: `MP000253` 코크스(4) · `MP000258` Grease(4) · `MP000259` Machine oil(3) · `MP000290` Nozzle(3) · `MP000293` 세유(3) · `MP000359` 중유(3) · `MP000301` 노즐(3) · `MP000303` 그라인더돌 12"ø(3) — 대부분 건설품셈 13장 플랜트설비의 소모품. + +### ② 지역·계약종별을 계산 때 푸는 연결 — 17줄 · 재료 3 + +- `MP000004` 모래 8줄(GC000211 · GC000677 · GC000685 · GC000933 · GC000934 · GC000938 · GF000176 · GF000183) +- `MP000358` 전력 8줄(GC000893∼897 · GC000933 · GC000934 · GC000938) +- `MP000006` 자갈 1줄(GF000183) + +연결이 `{이름, 지역=입력}`·`{이름, 계약종별=입력}` 이라 **줄마다 값이 입력(자재지역·전력 계약종별)에 따라 달라짐** — 대표 줄 하나로 굳히면 지역이 바뀔 때 틀린 값이 나옴. +모래·자갈은 산림 12장에서 쓴 방식(고르기 조건 + 단위중량 환산)을 건설품셈 줄에도 그대로 옮기면 되지만, 원문 단위가 ㎥ 와 ㎏ 로 갈려 **사용자 확정이 한 번 더 필요**해 이번엔 손대지 않음. + +### ③ 이름에 `{변수}` 가 든 참조 — 22줄 + +- `MT:{철선}` 5 · `MT:{결속선}` 5 · `MT:{각재}` 3 · `MT:{못}` 2 · `MT:{합판}` 2 · `MT:{박리제}` 1 · `MT:{용접봉}` 1 (19줄 · GC000031 · GC000037 · GC000062 · GC000068 · GC000127 · GC000207 · GC000211 · GC000224 · GF000159 · GF000160 · GF000187 · GF000196 · GF000207) +- `MP:VR관 ∅{관경}㎜` · `MP:고무링 ∅{관경}㎜` · `MP:커플링밴드 ∅{관경}㎜` (3줄 · GF000173 · GF000176) + +설계 입력(규격·관경)이 이름에 들어가는 줄 — 고르기 조건의 `규격` 칸에 `{관경}` 을 그대로 둘 수 있으나(엔진은 `{…}` 칸을 검사에서 뺌), **어느 구분·상세구분으로 좁힐지는 품목마다 달라** 표본 확인 뒤에 함. + +## 다음 + +1. ① 은 `ref/_없는_재료.md` 재료 채우기가 먼저 — 채워지면 `build_품셈재료_고르기.py` → `build_로직_재료_고르기.py` 순으로 다시 돌리면 자동으로 바뀜. +2. ② 는 모래·자갈 단위(㎥ ↔ ㎏) 확정 뒤 · 전력은 계약종별 입력을 고르기 조건으로 옮길지 확정 뒤. +3. ③ 은 품목 7가지 표본 확인 뒤 한 번에. +4. 품셈재료 테이블 삭제는 셋이 다 끝난 뒤(아직 안 함 — 지금도 178줄이 씀). diff --git a/resources/master_data/scripts/build_로직_재료_고르기.py b/resources/master_data/scripts/build_로직_재료_고르기.py index 3a727dc4..2effc236 100644 --- a/resources/master_data/scripts/build_로직_재료_고르기.py +++ b/resources/master_data/scripts/build_로직_재료_고르기.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- -"""로직 호표의 재료 줄을 「고르기 조건」 으로 바꿈 — 요소 「MP……」 → {구분, 상세구분[, 규격]}. +"""로직 호표의 재료 줄을 「고르기 조건」 으로 바꿈 — 요소 「MP……」·「MT……」 를 조건 묶음으로. - · 바탕 = 재료_품셈재료.json 의 「고르기」 칸 (`scripts/build_품셈재료_고르기.py` 가 채움) - · 고르기 칸이 빈 품셈재료(연결이 이미 있는 줄 · 짝 없는 줄)는 옛 방식 그대로 둠 + · 바탕 셋 — + ① 재료_품셈재료.json 의 「고르기」 칸 (`scripts/build_품셈재료_고르기.py` 가 채움) + ② 자재품목 줄을 바로 가리키는 「MT……」 — 그 줄의 구분·상세구분·규격 + 대표 = 그 줄 + ③ 연결이 자재품목 키 하나인 품셈재료 「MP……」 — 그 자재품목 줄로 ② 와 같게 + ②③ 은 대표 줄이 옛 요소와 같은 줄이라 금액이 그대로임(고르기 화면만 열림) + · 고르기 칸이 비고 연결도 지역·계약종별을 계산 때 푸는 줄(모래 · 자갈 · 전력)은 옛 방식 그대로 둠 · 줄 텍스트의 「"요소": "MP……"」 한 줄만 바꿈 — 나머지 글자·차례 그대로 · 다시 돌려도 같은 결과(이미 바뀐 줄은 건드릴 것이 없음) @@ -17,13 +21,36 @@ from pathlib import Path MASTER = Path(__file__).resolve().parents[1] SRC = MASTER / "재료_품셈재료.json" -LINE = re.compile(r'^(\s*"요소": )"(MP\d{6})"(,?)\s*$') +MARKET = MASTER / "재료_자재품목.json" +LINE = re.compile(r'^(\s*"요소": )"((?:MP|MT)\d{6})"(,?)\s*$') + + +def _cond_of(row: dict) -> dict | None: + """자재품목 줄 → 고르기 조건 · 대표는 그 줄(금액이 안 바뀌게).""" + if not row.get("구분"): + return None + cond = {"구분": row["구분"]} + for slot in ("상세구분", "규격"): + if row.get(slot): + cond[slot] = row[slot] + cond["대표"] = str(row["키"]) + return cond + + +def _picks() -> dict[str, dict]: + """요소 키 → 고르기 조건 — 품셈재료 고르기 칸 · 자재품목 줄 · 연결이 키 하나인 품셈재료.""" + market = {str(r["키"]): r for r in json.loads(MARKET.read_text(encoding="utf-8"))["줄"]} + picks = {key: cond for key, row in market.items() if (cond := _cond_of(row))} + for row in json.loads(SRC.read_text(encoding="utf-8"))["줄"]: + link = row.get("연결") + cond = row.get("고르기") or (picks.get(link) if isinstance(link, str) else None) + if cond: + picks[str(row["키"])] = cond + return picks def main(show: bool) -> int: - picks = { - str(r["키"]): r.get("고르기") for r in json.loads(SRC.read_text(encoding="utf-8"))["줄"] - } + picks = _picks() done, kept, files = 0, 0, 0 for path in sorted(MASTER.glob("로직_*.json")): text = path.read_text(encoding="utf-8") diff --git a/resources/master_data/로직_건설품셈_01장_도로포장공사.json b/resources/master_data/로직_건설품셈_01장_도로포장공사.json index ca4d6e2c..93bbfd93 100644 --- a/resources/master_data/로직_건설품셈_01장_도로포장공사.json +++ b/resources/master_data/로직_건설품셈_01장_도로포장공사.json @@ -3182,7 +3182,7 @@ }, { "종류": "재료", - "요소": "MT003175", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "일반용", "대표": "MT003175"}, "이름": "프로판가스(건설용 LPG)", "단위": "kg", "수량": "찾기(QC000546).프로판가스 / 10", diff --git a/resources/master_data/로직_건설품셈_01장_배관공사.json b/resources/master_data/로직_건설품셈_01장_배관공사.json index b46bb851..266dcf2a 100644 --- a/resources/master_data/로직_건설품셈_01장_배관공사.json +++ b/resources/master_data/로직_건설품셈_01장_배관공사.json @@ -125,7 +125,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소가스", "단위": "ℓ", "수량": "찾기(QC001401, '규격(㎜)'=규격).산소", @@ -134,7 +134,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌가스", "단위": "㎏", "수량": "찾기(QC001401, '규격(㎜)'=규격).아세틸렌 / 1000", diff --git a/resources/master_data/로직_건설품셈_01장_철골공사.json b/resources/master_data/로직_건설품셈_01장_철골공사.json index 105c3b27..931a8102 100644 --- a/resources/master_data/로직_건설품셈_01장_철골공사.json +++ b/resources/master_data/로직_건설품셈_01장_철골공사.json @@ -66,7 +66,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='산소').H형강부재, 찾기(QC001188, 재료='산소').전용접부재) * 1000", @@ -76,7 +76,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "L.P.G", "단위": "kg", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='L.P.G').H형강부재, 찾기(QC001188, 재료='L.P.G').전용접부재)", @@ -175,7 +175,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "만약(용접방식 == '수용접', 환산용접길이 * 찾기(QC001190, 재료='용접봉').수용접, 0)", @@ -835,7 +835,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "만약(가스 == '아세틸렌', 찾기(QC001202, '판두께(㎜)'=판두께).산소, 0) * 1000", @@ -845,7 +845,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "만약(가스 == '아세틸렌', 찾기(QC001202, '판두께(㎜)'=판두께).아세틸렌, 0)", @@ -854,7 +854,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "L.P.G", "단위": "kg", "수량": "만약(가스 == 'L.P.G', 찾기(QC001202, '판두께(㎜)'=판두께).'L.P.G', 0)", diff --git a/resources/master_data/로직_건설품셈_02장_가설공사.json b/resources/master_data/로직_건설품셈_02장_가설공사.json index 60fb8ce7..9c04cbb3 100644 --- a/resources/master_data/로직_건설품셈_02장_가설공사.json +++ b/resources/master_data/로직_건설품셈_02장_가설공사.json @@ -2056,7 +2056,7 @@ }, { "종류": "재료", - "요소": "MT007846", + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "1219×1700mm", "대표": "MT007846"}, "이름": "비계기본틀(기둥) H1700×W1219", "단위": "개", "수량": "찾기(QC000063, 구분='비계기본틀(기둥)', 규격='H1700×W1219').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2533,7 +2533,7 @@ }, { "종류": "재료", - "요소": "MT007180", + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "규격": "6mm", "대표": "MT007180"}, "이름": "와이어로프 ø6", "단위": "m", "수량": "찾기(QC000074, 구분='와이어로프', 규격='ø6').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -4642,7 +4642,7 @@ }, { "종류": "재료", - "요소": "MT008073", + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "규격": "하드롱지", "대표": "MT008073"}, "이름": "하드롱지", "단위": "㎡", "수량": "만약(작업 == '보양지 붙이기', 찾기(QC000144, 구분=작업, 자재='하드롱지').수량, 0)", diff --git a/resources/master_data/로직_건설품셈_02장_조적공사.json b/resources/master_data/로직_건설품셈_02장_조적공사.json index 04db6a0e..232d5dad 100644 --- a/resources/master_data/로직_건설품셈_02장_조적공사.json +++ b/resources/master_data/로직_건설품셈_02장_조적공사.json @@ -832,7 +832,7 @@ }, { "종류": "재료", - "요소": "MT005805", + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "조적용(25kg/포)", "대표": "MT005805"}, "이름": "ALC모르타르", "단위": "kg", "수량": "만약(블록규격mm == '600x400x100', 찾기(QC001223, '블록규격(mm)'='600×400×100').모르타르, 만약(블록규격mm == '600x400x125', 찾기(QC001223, '블록규격(mm)'='600×400×125').모르타르, 만약(블록규격mm == '600x300x150', 찾기(QC001223, '블록규격(mm)'='600×300×150').모르타르, 찾기(QC001223, '블록규격(mm)'='600×300×200').모르타르)))", diff --git a/resources/master_data/로직_건설품셈_02장_토목.json b/resources/master_data/로직_건설품셈_02장_토목.json index 66541db5..769fbd5b 100644 --- a/resources/master_data/로직_건설품셈_02장_토목.json +++ b/resources/master_data/로직_건설품셈_02장_토목.json @@ -3548,7 +3548,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "찾기(QC001951, 종별=난간규격).산소 * 1000 / 10", @@ -3556,7 +3556,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "찾기(QC001951, 종별=난간규격).아세틸렌 / 10", diff --git a/resources/master_data/로직_건설품셈_04장_궤도공사.json b/resources/master_data/로직_건설품셈_04장_궤도공사.json index da933c87..079a3b99 100644 --- a/resources/master_data/로직_건설품셈_04장_궤도공사.json +++ b/resources/master_data/로직_건설품셈_04장_궤도공사.json @@ -784,7 +784,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌가스", "단위": "kg", "수량": "찾기(QC000648, 품명='아세틸렌', 레일규격=레일규격).수량", @@ -793,7 +793,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000648, 품명='산소', 레일규격=레일규격, 규격='KSM 1101, 99.5%').수량 * 1000", @@ -950,7 +950,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000650, 품명='산소', 레일규격=레일규격).수량 * 1000", @@ -959,7 +959,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "프로판가스(건설용 LPG)", "단위": "kg", "수량": "찾기(QC000650, 품명='프로판가스', 레일규격=레일규격).수량", diff --git a/resources/master_data/로직_건설품셈_04장_조경공사.json b/resources/master_data/로직_건설품셈_04장_조경공사.json index ebd1d0b6..f5280ba4 100644 --- a/resources/master_data/로직_건설품셈_04장_조경공사.json +++ b/resources/master_data/로직_건설품셈_04장_조경공사.json @@ -190,7 +190,7 @@ }, { "종류": "재료", - "요소": "MP000075", + "요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "대표": "MT006489"}, "이름": "비료(복합비료)", "단위": "㎏", "수량": "찾기(QC000274, 구분='비료').수량 / 100", diff --git a/resources/master_data/로직_건설품셈_05장_강구조공사.json b/resources/master_data/로직_건설품셈_05장_강구조공사.json index af2ffa0b..8e1372b8 100644 --- a/resources/master_data/로직_건설품셈_05장_강구조공사.json +++ b/resources/master_data/로직_건설품셈_05장_강구조공사.json @@ -117,7 +117,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000664, 품명='산소').수량 * 1000", @@ -126,7 +126,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "LPG(건설용)", "단위": "kg", "수량": "찾기(QC000664, 품명='LPG가스').수량", diff --git a/resources/master_data/로직_건설품셈_05장_기초공사.json b/resources/master_data/로직_건설품셈_05장_기초공사.json index b4d3b301..4384bae2 100644 --- a/resources/master_data/로직_건설품셈_05장_기초공사.json +++ b/resources/master_data/로직_건설품셈_05장_기초공사.json @@ -4443,7 +4443,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "ℓ", "수량": "찾기(QC000377, 구분='산소', 규격=규격).수량", @@ -4451,7 +4451,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "LPG", "단위": "kg", "수량": "찾기(QC000377, 구분='LPG', 규격=규격).수량", diff --git a/resources/master_data/로직_건설품셈_05장_수장공사.json b/resources/master_data/로직_건설품셈_05장_수장공사.json index 5219505a..b2307df6 100644 --- a/resources/master_data/로직_건설품셈_05장_수장공사.json +++ b/resources/master_data/로직_건설품셈_05장_수장공사.json @@ -1864,7 +1864,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "㎏", "수량": "찾기(QC001288, 구분='외벽단열').시멘트", @@ -1937,7 +1937,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "㎏", "수량": "찾기(QC001288, 구분='하부보강').시멘트", diff --git a/resources/master_data/로직_건설품셈_06장_방수공사.json b/resources/master_data/로직_건설품셈_06장_방수공사.json index 73e0f419..e3642e52 100644 --- a/resources/master_data/로직_건설품셈_06장_방수공사.json +++ b/resources/master_data/로직_건설품셈_06장_방수공사.json @@ -976,7 +976,7 @@ }, { "종류": "재료", - "요소": "MP000356", + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT007749"}, "이름": "벤토나이트알갱이", "단위": "kg", "수량": "찾기(QC001307, 구분='벤토나이트알갱이', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -994,7 +994,7 @@ }, { "종류": "재료", - "요소": "MP000357", + "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "규격": "화약", "대표": "MT007751"}, "이름": "카트리지", "단위": "개", "수량": "찾기(QC001307, 구분='카트리지', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", diff --git a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json index 95bfe519..72572211 100644 --- a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json +++ b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json @@ -933,7 +933,7 @@ }, { "종류": "재료", - "요소": "MP000140", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (1kg=853ℓ)", "대표": "MT003164"}, "이름": "아세틸렌", "단위": "ℓ", "수량": "찾기(QC000423).아세틸렌", @@ -941,7 +941,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "ℓ", "수량": "찾기(QC000423).산소", @@ -1180,7 +1180,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "구멍땜 시멘트", "단위": "kg", "수량": "구멍땜 * 찾기(QC000430).시멘트 / 100", @@ -2134,7 +2134,7 @@ }, { "종류": "재료", - "요소": "MT006544", + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "200×5T", "대표": "MT006544"}, "이름": "PVC 지수판 200×5t", "단위": "m", "수량": "찾기(QC000449, 구분='PVC 지수판').수량", @@ -2142,7 +2142,7 @@ }, { "종류": "재료", - "요소": "MT003197", + "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "2.5mm", "대표": "MT003197"}, "이름": "PVC 용접봉", "단위": "kg", "수량": "찾기(QC000449, 구분='PVC 용접봉').수량", diff --git a/resources/master_data/로직_건설품셈_09장_미장공사.json b/resources/master_data/로직_건설품셈_09장_미장공사.json index cf9012c5..4064c0f7 100644 --- a/resources/master_data/로직_건설품셈_09장_미장공사.json +++ b/resources/master_data/로직_건설품셈_09장_미장공사.json @@ -51,7 +51,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001347, 배합용적비=배합용적비).시멘트", @@ -466,7 +466,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001356, 구분='시멘트').수량", @@ -524,7 +524,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001358, 구분='시멘트').수량", diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 52fe091e..da5f886d 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -1868,7 +1868,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001629, '관경 (외경)(㎜)'=관경, 항목='용접봉').수량", @@ -1957,7 +1957,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "찾기(QC001631, '구경 (㎜)'=구경, 구분=만약(SCH == '20∼40', '20∼40 산소(ℓ)', 만약(SCH == '60∼80', '60∼80 산소', '100∼160 산소'))).수량", @@ -1967,7 +1967,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "L.P.G", "단위": "kg", "수량": "찾기(QC001631, '구경 (㎜)'=구경, 구분=만약(SCH == '20∼40', '20∼40 LPG(kg)', 만약(SCH == '60∼80', '60∼80 LPG(kg)', '100∼160 LPG(kg)'))).수량", @@ -2038,7 +2038,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "산소량", @@ -2047,7 +2047,7 @@ }, { "종류": "재료", - "요소": "MT003176", + "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, "이름": "L.P.G", "단위": "kg", "수량": "LPG량", @@ -2192,7 +2192,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001635, '구경 ㎜'=구경, 'SCH No.'=SCH).용접봉", @@ -2405,7 +2405,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001638, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉사용량(㎏)').수량", @@ -2511,7 +2511,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001641, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉소비량(㎏)').수량", @@ -2625,7 +2625,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001642, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉소비량(㎏)').수량", @@ -2734,7 +2734,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001639, '자세 및 직종 두께(㎜)'=두께, 구분=만약(용접면 == '하향한면', '하향한면 용접', '하향양면 용접')).용접봉소비량", @@ -2843,7 +2843,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001640, '자세 및 직종 두께(㎜)'=두께, 구분=만약(용접면 == '하향한면', '하향한면 용접', '하향양면 용접')).용접봉소비량", @@ -7734,7 +7734,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.36 * 6000", @@ -7744,7 +7744,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.242", @@ -7753,7 +7753,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "2", @@ -8313,7 +8313,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.109 * 6000", @@ -8323,7 +8323,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.084", @@ -8332,7 +8332,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "0.365", @@ -8694,7 +8694,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "3.76 * 6000", @@ -8704,7 +8704,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "3.23", @@ -8726,7 +8726,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "24.99", @@ -8999,7 +8999,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "3 * 6000", @@ -9009,7 +9009,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "2.58", @@ -9031,7 +9031,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "20", @@ -9344,7 +9344,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.53 * 6000", @@ -9354,7 +9354,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.45", @@ -9363,7 +9363,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "6.2", @@ -9681,7 +9681,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.46 * 6000", @@ -9691,7 +9691,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.39", @@ -9700,7 +9700,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "5.4", @@ -9935,7 +9935,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.38 * 6000", @@ -9945,7 +9945,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.33", @@ -9954,7 +9954,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "3", @@ -10247,7 +10247,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "2.3 * 6000", @@ -10257,7 +10257,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.98", @@ -10279,7 +10279,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "14.35", @@ -10523,7 +10523,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.38 * 6000", @@ -10533,7 +10533,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.33", @@ -10542,7 +10542,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "3", @@ -10817,7 +10817,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.45 * 6000", @@ -10827,7 +10827,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.32", @@ -11036,7 +11036,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "1.35 * 6000", @@ -11046,7 +11046,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.57", @@ -11055,7 +11055,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "1.16", @@ -11100,7 +11100,7 @@ }, { "종류": "재료", - "요소": "MP000360", + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT003383"}, "이름": "징크프라이머", "단위": "ℓ", "수량": "0.246", @@ -11309,7 +11309,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "9.81", @@ -11327,7 +11327,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.55 * 6000", @@ -11337,7 +11337,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.39", @@ -11594,7 +11594,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "2.3 * 6000", @@ -11604,7 +11604,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.6", @@ -11626,7 +11626,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "54.6", @@ -11875,7 +11875,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.69 * 6000", @@ -11885,7 +11885,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.2", @@ -11894,7 +11894,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "31.05", @@ -12098,7 +12098,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "2.2 * 6000", @@ -12108,7 +12108,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.6", @@ -12326,7 +12326,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.5 * 6000", @@ -12336,7 +12336,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.05", @@ -12345,7 +12345,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "7", @@ -12515,7 +12515,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "1.805 * 6000", @@ -12525,7 +12525,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.275", @@ -12534,7 +12534,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "20.7", @@ -12815,7 +12815,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "2.2 * 6000", @@ -12825,7 +12825,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.5", @@ -12847,7 +12847,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "30.5", @@ -13057,7 +13057,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "1.89 * 6000", @@ -13067,7 +13067,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.8", @@ -13076,7 +13076,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "8", @@ -13271,7 +13271,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.029 * 6000", @@ -13281,7 +13281,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.012", @@ -13290,7 +13290,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "5.95", @@ -18095,7 +18095,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.2 * 6000", @@ -18105,7 +18105,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.13", @@ -18114,7 +18114,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "3.5", @@ -18403,7 +18403,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "0.68 * 6000", @@ -18413,7 +18413,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.58", @@ -18422,7 +18422,7 @@ }, { "종류": "재료", - "요소": "MT003187", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, "이름": "용접봉", "단위": "kg", "수량": "14.2", @@ -19057,7 +19057,7 @@ "호표": [ { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소", "단위": "L", "수량": "(만약(공사 == '기기', 0.109, 만약(공사 == '철골', 1.5, 만약(공사 == '배관', 5, 만약(공사 == 'Belt & Conveyor', 1.5, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.1, 만약(공사 == 'Crane 류', 0.44, 0)))))))) * 6000", @@ -19067,7 +19067,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌", "단위": "kg", "수량": "(만약(공사 == '기기', 0.084, 만약(공사 == '철골', 1.25, 만약(공사 == '배관', 3.7, 만약(공사 == 'Belt & Conveyor', 1.25, 만약(공사 == 'Heater & Tank', 0.08, 만약(공사 == 'Pump & Fan', 0.08, 만약(공사 == 'Crane 류', 0.355, 0))))))))", diff --git a/resources/master_data/로직_산림품셈_09장_토공.json b/resources/master_data/로직_산림품셈_09장_토공.json index 605bcfe0..5d36b33b 100644 --- a/resources/master_data/로직_산림품셈_09장_토공.json +++ b/resources/master_data/로직_산림품셈_09장_토공.json @@ -328,7 +328,7 @@ }, { "종류": "재료", - "요소": "MT003163", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, "이름": "아세틸렌가스", "단위": "kg", "수량": "찾기(QF000260, 이름='아세틸렌').적용", @@ -336,7 +336,7 @@ }, { "종류": "재료", - "요소": "MT003165", + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, "이름": "산소가스", "단위": "ℓ", "수량": "찾기(QF000260, 이름='산소').적용", diff --git a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json index dc6eded3..d0ed411b 100644 --- a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json +++ b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json @@ -190,7 +190,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "만약(수중 == '수중 콘크리트', 최대(만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트) * 1.3, 370), 만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트))", @@ -325,7 +325,7 @@ }, { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "만약(수중 == '수중 콘크리트', 최대(만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트) * 1.3, 370), 만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트))", @@ -902,7 +902,7 @@ }, { "종류": "재료", - "요소": "MP000017", + "요소": {"구분": "잡재료", "상세구분": "용수", "대표": "MT005758"}, "이름": "물", "단위": "ℓ", "수량": "3000 / 100", @@ -1378,7 +1378,7 @@ }, { "종류": "재료", - "요소": "MP000008", + "요소": {"구분": "철근·강재", "상세구분": "철근", "규격": "D=16mm", "대표": "MT014464"}, "이름": "연결철근(SD300, D16)", "단위": "ton", "수량": "연결철근량", @@ -2777,7 +2777,7 @@ }, { "종류": "재료", - "요소": "MP000009", + "요소": {"구분": "철근·강재", "상세구분": "철근", "규격": "D=13mm", "대표": "MT014463"}, "이름": "철근(SD300, D13)", "단위": "ton", "수량": "철근량", @@ -3930,7 +3930,7 @@ "호표": [ { "종류": "재료", - "요소": "MP000002", + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QF000385, 배합비=배합비).시멘트", @@ -4330,7 +4330,7 @@ "호표": [ { "종류": "재료", - "요소": "MT000481", + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "Φ48.6*2.3*4.0m", "대표": "MT000481"}, "이름": "강관(∅48.6㎜×2.4㎜)", "단위": "m", "수량": "찾기(QF000390, 구분='자재 - 강관(∅48.6mm×2.4mm)').적용", @@ -4338,7 +4338,7 @@ }, { "종류": "재료", - "요소": "MT000493", + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "대표": "MT000493"}, "이름": "이음철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 이음철물').적용", @@ -4346,7 +4346,7 @@ }, { "종류": "재료", - "요소": "MT000499", + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "규격": "직교,자재", "대표": "MT000499"}, "이름": "조임철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 조임철물').적용", @@ -4354,7 +4354,7 @@ }, { "종류": "재료", - "요소": "MT000505", + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "대표": "MT000505"}, "이름": "받침철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 받침철물').적용", @@ -4806,7 +4806,7 @@ "호표": [ { "종류": "재료", - "요소": "MT006544", + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "200×5T", "대표": "MT006544"}, "이름": "PVC 지수판(200×5)", "단위": "m", "수량": "찾기(QF000399, 구분='자재 - PVC 지수판(200×5)').적용", @@ -5251,7 +5251,7 @@ "호표": [ { "종류": "재료", - "요소": "MT009019", + "요소": {"구분": "전기·통신", "상세구분": "전선관·트레이", "규격": "HI-VE 54mm", "대표": "MT009019"}, "이름": "경질비닐전선관 54㎜", "단위": "m", "수량": "1", @@ -5280,7 +5280,7 @@ "호표": [ { "종류": "재료", - "요소": "MT006485", + "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "PE 필름(0.08mm)", "대표": "MT006485"}, "이름": "폴리에틸렌필름 t=0.08㎜", "단위": "㎡", "수량": "찾기(QF000408, 구분='재료').적용",