// ChemiFactory MES - Inventory and Material Property Management Logic
const API_INVENTORY = "/inventory";
const API_SUPPLIERS = "/suppliers";
let allInventories = [];
let allMaterials = [];
let allSuppliers = [];
document.addEventListener("DOMContentLoaded", () => {
initEvents();
fetchInventory();
fetchMaterials();
fetchSuppliers();
});
// Fetch API Data
async function fetchInventory() {
try {
const res = await fetch(`${API_INVENTORY}/`);
if (!res.ok) throw new Error("Failed to fetch inventory");
allInventories = await res.json();
renderInventoryList(allInventories);
} catch (err) {
console.error(err);
document.getElementById("inventory-list-body").innerHTML = `
| 재고 데이터를 불러오지 못했습니다. |
`;
}
}
async function fetchMaterials() {
try {
const res = await fetch(`${API_INVENTORY}/materials`);
if (!res.ok) throw new Error("Failed to fetch materials");
allMaterials = await res.json();
renderMaterialList(allMaterials);
bindMaterialDropdown(allMaterials);
} catch (err) {
console.error(err);
document.getElementById("material-list-body").innerHTML = `| 자재 데이터를 불러오지 못했습니다. |
`;
}
}
async function fetchSuppliers() {
try {
const res = await fetch(`${API_SUPPLIERS}/`);
if (!res.ok) throw new Error("Failed to fetch suppliers");
allSuppliers = await res.json();
bindSupplierDropdown(allSuppliers);
} catch (err) {
console.error(err);
}
}
// Renderers
function renderInventoryList(list) {
const tbody = document.getElementById("inventory-list-body");
tbody.innerHTML = "";
if (list.length === 0) {
tbody.innerHTML = `| 재고 입고 원장이 비어있습니다. |
`;
return;
}
list.forEach(inv => {
const tr = document.createElement("tr");
const statusBadge = inv.is_depleted
? `소진 완료`
: `재고 있음`;
tr.innerHTML = `
${inv.receipt_date} |
${inv.item_name || "-"} ${inv.item_number || "-"} |
${inv.material_name || "-"} (${inv.material_code || "-"}) |
${inv.receipt_quantity.toLocaleString()} |
${(inv.usage_quantity || 0).toLocaleString()} |
${(inv.stock || 0).toLocaleString()} |
${inv.unit_cost.toLocaleString()} 원 |
${inv.supplier || "-"} |
${statusBadge} |
|
`;
tbody.appendChild(tr);
});
}
function renderMaterialList(list) {
const tbody = document.getElementById("material-list-body");
tbody.innerHTML = "";
if (list.length === 0) {
tbody.innerHTML = `| 등록된 자재 기본 물성이 없습니다. |
`;
return;
}
list.forEach(mat => {
const tr = document.createElement("tr");
tr.innerHTML = `
${mat.material_code} |
${mat.material_name} |
${mat.material_type === 'Yarn' ? '원사 (Yarn)' : mat.material_type === 'Bond' ? '본드 (Bond)' : '기타'} |
${mat.density} g/cm³ |
${mat.remarks || "-"} |
|
`;
tbody.appendChild(tr);
});
}
function bindMaterialDropdown(materials) {
const select = document.getElementById("inv-material");
select.innerHTML = '';
materials.forEach(m => {
select.innerHTML += ``;
});
}
function bindSupplierDropdown(suppliers) {
const select = document.getElementById("inv-supplier");
select.innerHTML = '';
suppliers.forEach(s => {
select.innerHTML += ``;
});
}
// Events
function initEvents() {
// Tab switching
document.querySelectorAll(".tab-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
document.querySelectorAll(".tab-content").forEach(tc => tc.classList.remove("active"));
btn.classList.add("active");
document.getElementById(btn.dataset.tab).classList.add("active");
});
});
// Modal windows cancel
const modalIds = ["material-modal", "inventory-modal"];
modalIds.forEach(id => {
const modal = document.getElementById(id);
modal.querySelectorAll(".close-btn, .close-modal-btn").forEach(btn => {
btn.addEventListener("click", () => modal.style.display = "none");
});
});
// Material logic trigger
document.getElementById("btn-add-material").addEventListener("click", () => {
document.getElementById("material-form").reset();
document.getElementById("material-id").value = "";
document.getElementById("material-modal-title").innerText = "자재 물성 등록";
document.getElementById("material-modal").style.display = "flex";
});
document.getElementById("material-form").addEventListener("submit", handleMaterialSubmit);
// Inventory logic trigger
document.getElementById("btn-add-inventory").addEventListener("click", () => {
document.getElementById("inventory-form").reset();
document.getElementById("inventory-id").value = "";
document.getElementById("usage-group").style.display = "none";
document.getElementById("inv-date").value = new Date().toISOString().substring(0, 10);
document.getElementById("inventory-modal-title").innerText = "자재 신규 입고 등록";
document.getElementById("inventory-modal").style.display = "flex";
});
document.getElementById("inventory-form").addEventListener("submit", handleInventorySubmit);
// Search filters
document.getElementById("search-inventory").addEventListener("input", (e) => {
const query = e.target.value.toLowerCase();
const filtered = allInventories.filter(i =>
(i.item_name && i.item_name.toLowerCase().includes(query)) ||
(i.item_number && i.item_number.toLowerCase().includes(query)) ||
(i.material_name && i.material_name.toLowerCase().includes(query)) ||
(i.material_code && i.material_code.toLowerCase().includes(query))
);
renderInventoryList(filtered);
});
document.getElementById("search-material").addEventListener("input", (e) => {
const query = e.target.value.toLowerCase();
const filtered = allMaterials.filter(m =>
m.material_name.toLowerCase().includes(query) ||
m.material_code.toLowerCase().includes(query)
);
renderMaterialList(filtered);
});
}
// Submits Handlers
async function handleMaterialSubmit(e) {
e.preventDefault();
const id = document.getElementById("material-id").value;
const payload = {
material_code: document.getElementById("mat-code").value,
material_name: document.getElementById("mat-name").value,
material_type: document.getElementById("mat-type").value,
density: parseFloat(document.getElementById("mat-density").value),
remarks: document.getElementById("mat-remarks").value || null
};
try {
let res;
if (id) {
res = await fetch(`${API_INVENTORY}/materials/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
} else {
res = await fetch(`${API_INVENTORY}/materials`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
}
if (!res.ok) throw new Error("API failed");
document.getElementById("material-modal").style.display = "none";
fetchMaterials();
} catch (err) {
console.error(err);
alert("자재 규격 저장에 실패했습니다.");
}
}
window.openEditMaterialModal = function(mat) {
document.getElementById("material-id").value = mat.id;
document.getElementById("mat-code").value = mat.material_code;
document.getElementById("mat-name").value = mat.material_name;
document.getElementById("mat-type").value = mat.material_type;
document.getElementById("mat-density").value = mat.density;
document.getElementById("mat-remarks").value = mat.remarks || "";
document.getElementById("material-modal-title").innerText = "자재 물성 수정";
document.getElementById("material-modal").style.display = "flex";
};
window.deleteMaterial = async function(id) {
if (!confirm("이 자재 물성 규격을 정말 삭제하시겠습니까?\n이미 입고된 재고 이력이 존재할 경우 삭제가 거부될 수 있습니다.")) return;
try {
const res = await fetch(`${API_INVENTORY}/materials/${id}`, { method: "DELETE" });
if (!res.ok) {
const data = await res.json();
throw new Error(data.detail || "Check references");
}
fetchMaterials();
} catch (err) {
console.error(err);
alert(`자재 삭제 실패: ${err.message}`);
}
};
// Inventory Submits Handlers
async function handleInventorySubmit(e) {
e.preventDefault();
const id = document.getElementById("inventory-id").value;
const payload = {
material_id: parseInt(document.getElementById("inv-material").value),
supplier_id: parseInt(document.getElementById("inv-supplier").value),
receipt_date: document.getElementById("inv-date").value,
item_name: document.getElementById("inv-item-name").value || null,
item_number: document.getElementById("inv-item-num").value || null,
receipt_quantity: parseFloat(document.getElementById("inv-qty").value),
usage_quantity: id ? parseFloat(document.getElementById("inv-usage").value || 0) : 0.0,
unit_cost: parseFloat(document.getElementById("inv-cost").value),
remarks: document.getElementById("inv-remarks").value || null
};
try {
let res;
if (id) {
res = await fetch(`${API_INVENTORY}/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
} else {
res = await fetch(`${API_INVENTORY}/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
}
if (!res.ok) throw new Error("API failed");
document.getElementById("inventory-modal").style.display = "none";
fetchInventory();
} catch (err) {
console.error(err);
alert("재고 정보를 저장하는 데 실패했습니다.");
}
}
window.openEditInventoryModal = function(inv) {
document.getElementById("inventory-id").value = inv.id;
document.getElementById("inv-material").value = inv.material_id;
document.getElementById("inv-supplier").value = inv.supplier_id;
document.getElementById("inv-date").value = inv.receipt_date;
document.getElementById("inv-item-name").value = inv.item_name || "";
document.getElementById("inv-item-num").value = inv.item_number || "";
document.getElementById("inv-qty").value = inv.receipt_quantity;
document.getElementById("inv-cost").value = inv.unit_cost;
document.getElementById("inv-remarks").value = inv.remarks || "";
// Show usage input when modifying
document.getElementById("usage-group").style.display = "block";
document.getElementById("inv-usage").value = inv.usage_quantity || 0;
document.getElementById("inventory-modal-title").innerText = "재고 정보 수정";
document.getElementById("inventory-modal").style.display = "flex";
};
window.depleteInventory = async function(id) {
if (!confirm("이 입고 건의 잔량 재고를 모두 소진 처리하시겠습니까?")) return;
try {
const res = await fetch(`${API_INVENTORY}/${id}/deplete`, { method: "PUT" });
if (!res.ok) throw new Error("Failed to deplete");
fetchInventory();
} catch (err) {
console.error(err);
alert("재고 소진 처리에 실패했습니다.");
}
};