const EstSchedule = (() => { function day(value) { if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) throw Error("공정 날짜는 YYYY-MM-DD 형식입니다."); const time = Date.parse(value + "T00:00:00Z"); if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== value) throw Error("공정 날짜 오류"); return time / 86400000; } function validate(project) { if (project.schedule == null) return; if (!Array.isArray(project.schedule) || project.schedule.length > 10000) throw Error("공정표 형식/행 수 오류"); const ids = new Set(); for (const row of project.schedule) { if (!row || typeof row.id !== "string" || !row.id || ids.has(row.id)) throw Error("공정 항목 ID 오류"); ids.add(row.id); for (const key of ["Code", "Name", "start", "end"]) if (typeof row[key] !== "string" || row[key].length > 1000) throw Error("공정 문자열 오류"); for (const key of ["weight", "progress"]) if ( typeof row[key] !== "number" || !Number.isFinite(row[key]) || row[key] < 0 || row[key] > Number.MAX_SAFE_INTEGER ) throw Error("공정 수치 오류"); if (row.progress > 100) throw Error("공정 진도는 0~100%입니다."); if (row.start) day(row.start); if (row.end) day(row.end); if (row.start && row.end && day(row.start) > day(row.end)) throw Error("공정 종료일이 시작일보다 빠릅니다."); } } function calculate(project) { validate(project); const rows = project.schedule || [], dated = rows.filter((r) => r.start && r.end), first = dated.length ? Math.min(...dated.map((r) => day(r.start))) : 0, last = dated.length ? Math.max(...dated.map((r) => day(r.end))) : first, span = last - first + 1, weight = rows.reduce((sum, r) => sum + r.weight, 0); return { rows: rows.map((r) => ({ ...r, left: r.start && r.end ? ((day(r.start) - first) / span) * 100 : 0, width: r.start && r.end ? ((day(r.end) - day(r.start) + 1) / span) * 100 : 0, })), weight, progress: weight ? rows.reduce((sum, r) => sum + r.weight * r.progress, 0) / weight : 0, start: dated.length ? new Date(first * 86400000).toISOString().slice(0, 10) : "", end: dated.length ? new Date(last * 86400000).toISOString().slice(0, 10) : "", }; } function render() { const { esc, number, state } = EstApp, output = calculate(state.project); EstApp.byId("work-content").innerHTML = `
기간과 진도는 직접 입력합니다. 가중치는 공종 금액 또는 사용자가 정한 값을 사용합니다.
| ${h} | `).join("")}||
|---|---|---|
| `).join("")} |