Merge remote-tracking branches 'origin/main_desktop_1', 'origin/sub_laptop_1' and 'origin/sub_laptop_4' into sub_laptop_3
This commit is contained in:
@@ -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<LogicHandle> {
|
||||
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],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<string, DraftDecided> = {};
|
||||
const rows = new Map<string, VarRow>();
|
||||
const values: Record<string, string> = {};
|
||||
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<number, DraftProblem[]>();
|
||||
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<void> => {
|
||||
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<void> {
|
||||
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),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -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<FileInfo | undefined> {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<string, string> };
|
||||
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);
|
||||
},
|
||||
}),
|
||||
]);
|
||||
})();
|
||||
});
|
||||
}
|
||||
@@ -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<string, readonly [string, string]>;
|
||||
|
||||
export type NewTextKey = keyof typeof TEXT;
|
||||
|
||||
export function tn(key: NewTextKey, fill: Record<string, string | number> = {}): 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));
|
||||
}
|
||||
@@ -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<string, DraftDecided>;
|
||||
/** 고른 것이 바뀜 — 화면이 검사를 다시 부름 */
|
||||
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<string, string> = { 인력: "노무비", 재료: "재료비", 기계: "경비" };
|
||||
|
||||
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<string, unknown>)[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 };
|
||||
}
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
/** 변수마다 정한 것 */
|
||||
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<string, DraftDecided>;
|
||||
head?: DraftHead;
|
||||
save?: boolean;
|
||||
owner?: string;
|
||||
}): Promise<DraftAnswer> => request("/logic/draft", body);
|
||||
|
||||
@@ -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;
|
||||
/** 로직마다 넣은 값 — 다시 그려도 남음 */
|
||||
|
||||
@@ -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줄이 씀).
|
||||
@@ -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")
|
||||
|
||||
@@ -3182,7 +3182,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003175",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "일반용", "대표": "MT003175"},
|
||||
"이름": "프로판가스(건설용 LPG)",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC000546).프로판가스 / 10",
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003165",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"},
|
||||
"이름": "산소가스",
|
||||
"단위": "ℓ",
|
||||
"수량": "찾기(QC001401, '규격(㎜)'=규격).산소",
|
||||
@@ -134,7 +134,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003163",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"},
|
||||
"이름": "아세틸렌가스",
|
||||
"단위": "㎏",
|
||||
"수량": "찾기(QC001401, '규격(㎜)'=규격).아세틸렌 / 1000",
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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').모르타르)))",
|
||||
|
||||
@@ -3548,7 +3548,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003165",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"},
|
||||
"이름": "산소",
|
||||
"단위": "L",
|
||||
"수량": "찾기(QC001951, 종별=난간규격).산소 * 1000 / 10",
|
||||
@@ -3556,7 +3556,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003163",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"},
|
||||
"이름": "아세틸렌",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC001951, 종별=난간규격).아세틸렌 / 10",
|
||||
|
||||
@@ -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, 품명='프로판가스', 레일규격=레일규격).수량",
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000075",
|
||||
"요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "대표": "MT006489"},
|
||||
"이름": "비료(복합비료)",
|
||||
"단위": "㎏",
|
||||
"수량": "찾기(QC000274, 구분='비료').수량 / 100",
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003165",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"},
|
||||
"이름": "산소가스",
|
||||
"단위": "L",
|
||||
"수량": "찾기(QC000664, 품명='산소').수량 * 1000",
|
||||
@@ -126,7 +126,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003176",
|
||||
"요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"},
|
||||
"이름": "LPG(건설용)",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC000664, 품명='LPG가스').수량",
|
||||
|
||||
@@ -4443,7 +4443,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003165",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"},
|
||||
"이름": "산소",
|
||||
"단위": "ℓ",
|
||||
"수량": "찾기(QC000377, 구분='산소', 규격=규격).수량",
|
||||
@@ -4451,7 +4451,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003176",
|
||||
"요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"},
|
||||
"이름": "LPG",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC000377, 구분='LPG', 규격=규격).수량",
|
||||
|
||||
@@ -1864,7 +1864,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000002",
|
||||
"요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"},
|
||||
"이름": "시멘트",
|
||||
"단위": "㎏",
|
||||
"수량": "찾기(QC001288, 구분='외벽단열').시멘트",
|
||||
@@ -1937,7 +1937,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000002",
|
||||
"요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"},
|
||||
"이름": "시멘트",
|
||||
"단위": "㎏",
|
||||
"수량": "찾기(QC001288, 구분='하부보강').시멘트",
|
||||
|
||||
@@ -976,7 +976,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000356",
|
||||
"요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT007749"},
|
||||
"이름": "벤토나이트알갱이",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC001307, 구분='벤토나이트알갱이', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량",
|
||||
@@ -994,7 +994,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000357",
|
||||
"요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "규격": "화약", "대표": "MT007751"},
|
||||
"이름": "카트리지",
|
||||
"단위": "개",
|
||||
"수량": "찾기(QC001307, 구분='카트리지', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량",
|
||||
|
||||
@@ -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 용접봉').수량",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000002",
|
||||
"요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"},
|
||||
"이름": "시멘트",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC001347, 배합용적비=배합용적비).시멘트",
|
||||
@@ -466,7 +466,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000002",
|
||||
"요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"},
|
||||
"이름": "시멘트",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC001356, 구분='시멘트').수량",
|
||||
@@ -524,7 +524,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MP000002",
|
||||
"요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"},
|
||||
"이름": "시멘트",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QC001358, 구분='시멘트').수량",
|
||||
|
||||
@@ -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))))))))",
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003163",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"},
|
||||
"이름": "아세틸렌가스",
|
||||
"단위": "kg",
|
||||
"수량": "찾기(QF000260, 이름='아세틸렌').적용",
|
||||
@@ -336,7 +336,7 @@
|
||||
},
|
||||
{
|
||||
"종류": "재료",
|
||||
"요소": "MT003165",
|
||||
"요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"},
|
||||
"이름": "산소가스",
|
||||
"단위": "ℓ",
|
||||
"수량": "찾기(QF000260, 이름='산소').적용",
|
||||
|
||||
@@ -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, 구분='재료').적용",
|
||||
|
||||
Reference in New Issue
Block a user