초기화
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
// ChemiFactory MES - Productivity Cost Simulator Frontend Logic
|
||||
const API_INVENTORY = "/inventory";
|
||||
|
||||
let allInventories = [];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
fetchInventoryDropdowns();
|
||||
document.getElementById("simulator-form").addEventListener("submit", runSimulation);
|
||||
document.getElementById("adjustment-form").addEventListener("submit", runAdjustmentCalculator);
|
||||
document.getElementById("dryer-form").addEventListener("submit", runDryingCalculator);
|
||||
});
|
||||
|
||||
async function fetchInventoryDropdowns() {
|
||||
try {
|
||||
const res = await fetch(`${API_INVENTORY}/`);
|
||||
if (!res.ok) throw new Error("Failed to fetch inventory dropdowns");
|
||||
allInventories = await res.json();
|
||||
|
||||
const yarns = allInventories.filter(i => i.material_type && (i.material_type.toLowerCase() === 'yarn' || i.material_type === '원사') && i.is_depleted !== 1);
|
||||
const bonds = allInventories.filter(i => i.material_type && (i.material_type.toLowerCase() === 'bond' || i.material_type === '본드') && i.is_depleted !== 1);
|
||||
|
||||
const yarnSelect = document.getElementById("sim-yarn");
|
||||
yarnSelect.innerHTML = '<option value="">-- 원재료 원사 선택 --</option>';
|
||||
yarns.forEach(y => {
|
||||
yarnSelect.innerHTML += `<option value="${y.id}">${y.item_name || y.material_name} (재고: ${y.stock}kg)</option>`;
|
||||
});
|
||||
|
||||
const bondSelect = document.getElementById("sim-bond");
|
||||
bondSelect.innerHTML = '<option value="">-- 원재료 본드 선택 (선택사항) --</option>';
|
||||
bonds.forEach(b => {
|
||||
bondSelect.innerHTML += `<option value="${b.id}">${b.item_name || b.material_name} (재고: ${b.stock}kg)</option>`;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function runSimulation(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const yarnId = document.getElementById("sim-yarn").value;
|
||||
const qty = document.getElementById("sim-qty").value;
|
||||
|
||||
if (!yarnId || !qty) {
|
||||
alert("원사 자재와 목표 수량을 올바르게 지정하십시오.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
inputs: {
|
||||
yarn_inventory_id: parseInt(yarnId),
|
||||
bond_inventory_id: parseInt(document.getElementById("sim-bond").value) || null,
|
||||
yarn_diameter_micron: parseFloat(document.getElementById("sim-dia").value),
|
||||
yarn_k: parseInt(document.getElementById("sim-k").value),
|
||||
ports: parseInt(document.getElementById("sim-ports").value),
|
||||
cycle_time_hz: parseFloat(document.getElementById("sim-hz").value),
|
||||
cut_length_mm: parseFloat(document.getElementById("sim-cut").value),
|
||||
work_hours_day: parseInt(document.getElementById("sim-hours").value),
|
||||
work_days_month: 20.0,
|
||||
num_machines: parseFloat(document.getElementById("sim-machines").value),
|
||||
bond_percentage: parseFloat(document.getElementById("sim-bond-pct").value) || 3.0
|
||||
},
|
||||
targetProductionQuantity: parseFloat(qty)
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_INVENTORY}/analysis/calculate`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errData = await res.json();
|
||||
throw new Error(errData.detail || "시뮬레이션 연산 실패");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Bind 결과 데이터
|
||||
document.getElementById("res-days").innerText = `${data.required_days_target.toFixed(1)} 일`;
|
||||
document.getElementById("res-fee").innerText = `${Math.round(data.plan_processing_fee).toLocaleString()} 원`;
|
||||
document.getElementById("res-sales").innerText = `${Math.round(data.plan_estimated_sales).toLocaleString()} 원`;
|
||||
document.getElementById("res-margin").innerText = `${Math.round(data.plan_company_margin).toLocaleString()} 원`;
|
||||
document.getElementById("res-yarn-cost").innerText = `${Math.round(data.plan_yarn_cost).toLocaleString()} 원`;
|
||||
document.getElementById("res-bond-cost").innerText = `${Math.round(data.plan_bond_cost).toLocaleString()} 원`;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(`시뮬레이션 가동 중 오류 발생: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function runAdjustmentCalculator(e) {
|
||||
e.preventDefault();
|
||||
const resultBox = document.getElementById("adj-result-box");
|
||||
const resultsContainer = document.getElementById("adj-results");
|
||||
|
||||
resultBox.style.display = "none";
|
||||
resultsContainer.innerHTML = "";
|
||||
|
||||
const cRaw = parseFloat(document.getElementById("adj-raw-conc").value);
|
||||
const vCurr = parseFloat(document.getElementById("adj-curr-vol").value);
|
||||
const cCurr = parseFloat(document.getElementById("adj-curr-conc").value);
|
||||
const vTarget = parseFloat(document.getElementById("adj-tgt-vol").value);
|
||||
const cTarget = parseFloat(document.getElementById("adj-tgt-conc").value);
|
||||
|
||||
if ([cRaw, vCurr, cCurr, vTarget, cTarget].some(val => isNaN(val))) {
|
||||
alert("모든 필드에 유효한 숫자를 입력해 주십시오.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (vTarget < vCurr) {
|
||||
alert("목표 부피는 현재 부피보다 작을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cRaw <= 0 || cTarget < 0 || cCurr < 0 || cRaw > 100 || cTarget > 100 || cCurr > 100) {
|
||||
alert("농도 값은 0%에서 100% 사이여야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cTarget > cRaw) {
|
||||
alert("목표 농도는 원자재 본드 농도보다 높을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const cRawDec = cRaw / 100;
|
||||
const cCurrDec = cCurr / 100;
|
||||
const cTargetDec = cTarget / 100;
|
||||
|
||||
const currentBond = vCurr * cCurrDec;
|
||||
const targetBond = vTarget * cTargetDec;
|
||||
|
||||
if (targetBond < currentBond - 0.001) {
|
||||
alert("계산 불가: 목표 본드량이 현재 본드량보다 적습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
let rawMaterialToAdd = 0;
|
||||
if (cRawDec > 0) {
|
||||
rawMaterialToAdd = (targetBond - currentBond) / cRawDec;
|
||||
}
|
||||
if (rawMaterialToAdd < 0) rawMaterialToAdd = 0;
|
||||
|
||||
const waterToAdd = vTarget - vCurr - rawMaterialToAdd;
|
||||
|
||||
if (waterToAdd < -0.001) {
|
||||
if (cRaw - cTarget > 0) {
|
||||
const suggestedMinVolume = vCurr * (cRaw - cCurr) / (cRaw - cTarget);
|
||||
alert(`목표 부피(${vTarget.toFixed(1)}L)로는 목표 농도(${cTarget}%)를 맞출 수 없습니다.\n해당 농도를 맞추기 위한 최소 목표 부피는 ${suggestedMinVolume.toFixed(2)}L 입니다.`);
|
||||
} else {
|
||||
alert("계산 불가: 목표 농도가 원자재 농도와 같거나 높아 도달할 수 없습니다.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const finalPureBond = targetBond;
|
||||
const finalTotalWater = vTarget - finalPureBond;
|
||||
|
||||
resultsContainer.innerHTML = `
|
||||
<p style="color:var(--text-secondary);">• 추가할 물: <strong style="color:#ffffff;">${waterToAdd.toFixed(2)} L</strong></p>
|
||||
<p style="color:var(--text-secondary);">• 추가할 원자재: <strong style="color:#ffffff;">${rawMaterialToAdd.toFixed(2)} L</strong></p>
|
||||
<hr style="border-color:var(--border-color); margin: 6px 0;">
|
||||
<p style="color:var(--text-secondary);">• 최종 용액 총 부피: <strong style="color:var(--accent-green);">${vTarget.toFixed(1)} L</strong></p>
|
||||
<p style="color:var(--text-muted); font-size:12px; margin-left: 10px;">- 순수 본드 성분: ${finalPureBond.toFixed(2)} L</p>
|
||||
<p style="color:var(--text-muted); font-size:12px; margin-left: 10px;">- 포함된 총 물: ${finalTotalWater.toFixed(2)} L</p>
|
||||
`;
|
||||
|
||||
resultBox.style.display = "block";
|
||||
}
|
||||
|
||||
function runDryingCalculator(e) {
|
||||
e.preventDefault();
|
||||
const resultBox = document.getElementById("dry-result-box");
|
||||
const resultsContainer = document.getElementById("dry-results");
|
||||
|
||||
resultBox.style.display = "none";
|
||||
resultsContainer.innerHTML = "";
|
||||
|
||||
const speed = parseFloat(document.getElementById("dry-speed").value);
|
||||
const concentration = parseFloat(document.getElementById("dry-conc").value);
|
||||
const usage = parseFloat(document.getElementById("dry-usage").value);
|
||||
const loss = parseFloat(document.getElementById("dry-loss").value);
|
||||
|
||||
if ([speed, concentration, usage, loss].some(val => isNaN(val))) {
|
||||
alert("모든 필드에 유효한 숫자를 입력해 주십시오.");
|
||||
return;
|
||||
}
|
||||
|
||||
const usageMlPerSec = (usage * 1000) / 86400; // L/day -> ml/s
|
||||
const waterFraction = 1 - (concentration / 100);
|
||||
const waterRateGps = usageMlPerSec * waterFraction;
|
||||
const latentHeat = 2260; // J/g
|
||||
const powerW = waterRateGps * latentHeat;
|
||||
const recommendedPowerW = powerW * (1 + (loss / 100));
|
||||
|
||||
resultsContainer.innerHTML = `
|
||||
<p style="color:var(--text-secondary);">• 이론상 최소 필요 전력: <strong style="color:#ffffff;">${powerW.toFixed(2)} W</strong></p>
|
||||
<p style="color:var(--text-secondary);">• 권장 히터 동작 전력 (Loss 포함): <strong style="color:var(--accent-orange);">${recommendedPowerW.toFixed(2)} W</strong></p>
|
||||
<p style="color:var(--text-muted); font-size: 11px; margin-top: 6px;">* 물의 잠열 ${latentHeat} J/g 및 입력 건조 손실률 ${loss}%를 대입한 추산치입니다.</p>
|
||||
`;
|
||||
|
||||
resultBox.style.display = "block";
|
||||
}
|
||||
Reference in New Issue
Block a user