feat(B09): 공구손료·잡재료 칸 — 기본은 빔, 넣으면 주재료비의 %로 붙음
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」. 근거는 산림품셈 1-2-6 — 주재료비(할증수량 제외)의 2~5%까지, 산정 근거 명시. - 기초자료 탭 「산출 조건」에 칸 하나 + [적용]. 비면 줄 자체가 안 섬(지금 상태 그대로). - 밑수는 **자재 줄만** — 노무·경비, 하위 일위대가 재료비는 안 듦(층마다 거듭 세지 않음). - 상한 5% 초과는 거절(400) — 조용히 깎아 넣지 않음. - ⚠ 지금은 일위대가에 주재료비가 선 공종이 0개라 붙을 밑수가 없음 — 그 사실을 칸 밑에 띄움. - 곁다리: 비율 줄(제잡비·공구손료)이 저장했다 읽으면 사라지던 것을 고침. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -486,10 +486,22 @@ export interface MachineChoiceRow {
|
||||
basis: string[];
|
||||
}
|
||||
|
||||
/** 공구손료·잡재료 칸 — **비어 있는 것이 기본**이고, 비면 안 붙는다(산림품셈 1-2-6). */
|
||||
export interface MiscMaterialRow {
|
||||
percent: string;
|
||||
min: string;
|
||||
max: string;
|
||||
basis: string[];
|
||||
/** 주재료비가 선 일위대가 수 — 0 이면 넣어도 붙을 밑수가 없다. */
|
||||
base_items: number;
|
||||
base_note: string;
|
||||
}
|
||||
|
||||
export interface FactorChoicesDto {
|
||||
status: string;
|
||||
ranges: RangeFactorRow[];
|
||||
machines: MachineChoiceRow[];
|
||||
misc_material?: MiscMaterialRow;
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
@@ -504,7 +516,11 @@ export async function fetchFactorChoices(projectId: string): Promise<FactorChoic
|
||||
|
||||
export async function saveFactorChoices(
|
||||
projectId: string,
|
||||
body: { range_factor_choices?: Record<string, string>; machine_choices?: Record<string, string> },
|
||||
body: {
|
||||
range_factor_choices?: Record<string, string>;
|
||||
machine_choices?: Record<string, string>;
|
||||
misc_material_percent?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/estimation/factors`,
|
||||
@@ -515,7 +531,56 @@ export async function saveFactorChoices(
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
if (!response.ok) throw new Error(`factors save ${response.status}`);
|
||||
if (!response.ok) {
|
||||
const message = await response
|
||||
.json()
|
||||
.then((body: { message?: string }) => body.message ?? "")
|
||||
.catch(() => "");
|
||||
throw new Error(message || `factors save ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 숫자 칸 하나 — **빈 칸이 기본**이다. [적용]을 눌러야 저장된다.
|
||||
*
|
||||
* ⚠ 고르는 칸(`picker`)과 달리 여기는 **사용자가 값을 짓는 자리**라 누를 때만 보낸다 —
|
||||
* 타자 한 자마다 보내면 「2」를 치는 도중에 2% 로 저장돼 버린다.
|
||||
*/
|
||||
function percentBox(
|
||||
label: string,
|
||||
value: string,
|
||||
placeholder: string,
|
||||
onApply: (text: string) => void,
|
||||
): HTMLElement {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "b09-hint";
|
||||
wrap.style.display = "flex";
|
||||
wrap.style.alignItems = "center";
|
||||
wrap.style.gap = "8px";
|
||||
wrap.style.flexWrap = "wrap";
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.style.fontWeight = "600";
|
||||
name.textContent = label;
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "number";
|
||||
input.step = "0.1";
|
||||
input.min = "0";
|
||||
input.value = value;
|
||||
input.placeholder = placeholder;
|
||||
input.style.width = "72px";
|
||||
|
||||
const unit = document.createElement("span");
|
||||
unit.textContent = "%";
|
||||
|
||||
const apply = document.createElement("button");
|
||||
apply.type = "button";
|
||||
apply.textContent = "적용";
|
||||
apply.addEventListener("click", () => onApply(input.value.trim()));
|
||||
|
||||
wrap.append(name, input, unit, apply);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function picker(
|
||||
@@ -599,5 +664,27 @@ export function drawFactorChoices(
|
||||
for (const line of row.basis) body.append(note(line));
|
||||
}
|
||||
|
||||
const misc = data.misc_material;
|
||||
if (misc) {
|
||||
body.append(
|
||||
percentBox("공구손료·잡재료 (주재료비의)", misc.percent, "비움", (text) => {
|
||||
void saveFactorChoices(projectId, { misc_material_percent: text })
|
||||
.then(reload)
|
||||
.catch((error: Error) => {
|
||||
body.append(note(`⚠ ${error.message}`));
|
||||
});
|
||||
}),
|
||||
);
|
||||
body.append(
|
||||
note(
|
||||
misc.percent
|
||||
? `지금 ${misc.percent}% 로 붙고 있습니다 — 칸을 비우고 [적용]하면 도로 안 붙습니다.`
|
||||
: `비어 있어 안 붙고 있습니다 — 넣을 수 있는 값은 ${misc.min}~${misc.max}% 입니다.`,
|
||||
),
|
||||
);
|
||||
if (misc.base_note) body.append(note(misc.base_note));
|
||||
for (const line of misc.basis) body.append(note(line));
|
||||
}
|
||||
|
||||
for (const line of data.notes) body.append(note(line));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user