초기화
This commit is contained in:
@@ -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, '"')})">수정</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, '"')})">수정</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("재고 소진 처리에 실패했습니다.");
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user