초기화

This commit is contained in:
2026-07-15 18:37:19 +09:00
commit 94abc5461d
1268 changed files with 380198 additions and 0 deletions
+327
View File
@@ -0,0 +1,327 @@
// 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 = `<tr><td colspan="10" style="text-align:center; color:var(--accent-red); padding:20px 0;">재고 데이터를 불러오지 못했습니다.</td></tr>`;
}
}
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 = `<tr><td colspan="6" style="text-align:center; color:var(--accent-red); padding:20px 0;">자재 데이터를 불러오지 못했습니다.</td></tr>`;
}
}
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 = `<tr><td colspan="10" style="text-align:center; color:var(--text-muted); padding:30px 0;">재고 입고 원장이 비어있습니다.</td></tr>`;
return;
}
list.forEach(inv => {
const tr = document.createElement("tr");
const statusBadge = inv.is_depleted
? `<span class="depleted-badge">소진 완료</span>`
: `<span class="active-badge">재고 있음</span>`;
tr.innerHTML = `
<td>${inv.receipt_date}</td>
<td><strong>${inv.item_name || "-"}</strong><br><small style="color:var(--text-muted);">${inv.item_number || "-"}</small></td>
<td>${inv.material_name || "-"} (${inv.material_code || "-"})</td>
<td>${inv.receipt_quantity.toLocaleString()}</td>
<td>${(inv.usage_quantity || 0).toLocaleString()}</td>
<td style="font-weight:700; color:${inv.stock <= 0 ? 'var(--accent-red)' : 'var(--text-primary)'}">${(inv.stock || 0).toLocaleString()}</td>
<td>${inv.unit_cost.toLocaleString()} 원</td>
<td>${inv.supplier || "-"}</td>
<td>${statusBadge}</td>
<td>
<button class="btn btn-secondary" style="padding:4px 8px; font-size:11px;" onclick="openEditInventoryModal(${JSON.stringify(inv).replace(/"/g, '&quot;')})">수정</button>
<button class="btn btn-danger" style="padding:4px 8px; font-size:11px;" onclick="depleteInventory(${inv.id})">소진</button>
</td>
`;
tbody.appendChild(tr);
});
}
function renderMaterialList(list) {
const tbody = document.getElementById("material-list-body");
tbody.innerHTML = "";
if (list.length === 0) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; color:var(--text-muted); padding:30px 0;">등록된 자재 기본 물성이 없습니다.</td></tr>`;
return;
}
list.forEach(mat => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td><code>${mat.material_code}</code></td>
<td><strong>${mat.material_name}</strong></td>
<td>${mat.material_type === 'Yarn' ? '원사 (Yarn)' : mat.material_type === 'Bond' ? '본드 (Bond)' : '기타'}</td>
<td>${mat.density} g/cm³</td>
<td>${mat.remarks || "-"}</td>
<td>
<button class="btn btn-secondary" style="padding:4px 8px; font-size:11px;" onclick="openEditMaterialModal(${JSON.stringify(mat).replace(/"/g, '&quot;')})">수정</button>
<button class="btn btn-danger" style="padding:4px 8px; font-size:11px;" onclick="deleteMaterial(${mat.id})">삭제</button>
</td>
`;
tbody.appendChild(tr);
});
}
function bindMaterialDropdown(materials) {
const select = document.getElementById("inv-material");
select.innerHTML = '<option value="">-- 자재 규격 선택 --</option>';
materials.forEach(m => {
select.innerHTML += `<option value="${m.id}">${m.material_name} (${m.material_code})</option>`;
});
}
function bindSupplierDropdown(suppliers) {
const select = document.getElementById("inv-supplier");
select.innerHTML = '<option value="">-- 공급사 선택 --</option>';
suppliers.forEach(s => {
select.innerHTML += `<option value="${s.id}">${s.name_kr}</option>`;
});
}
// 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("재고 소진 처리에 실패했습니다.");
}
};