feat(purchase): 카테고리 테이블 분리 + 동적 로드 + tkuser 관리
- DB: consumable_categories 테이블 생성, ENUM→VARCHAR 변환, 시드 4개 - API: GET/POST/PUT/DEACTIVATE /api/consumable-categories - 프론트: 3개 JS 하드코딩 CAT_LABELS 제거 → API loadCategories() 동적 로드 - tkuser: 카테고리 관리 섹션 추가, select 옵션 동적 생성 - 별칭 시드 SQL (INSERT IGNORE 기반) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,23 @@
|
||||
/* ===== 구매 분석 페이지 ===== */
|
||||
const CAT_LABELS = { consumable: '소모품', safety: '안전용품', repair: '수선비', equipment: '설비' };
|
||||
const CAT_ICONS = { consumable: 'fa-box', safety: 'fa-hard-hat', repair: 'fa-wrench', equipment: 'fa-cogs' };
|
||||
const CAT_BG = { consumable: 'bg-blue-50 text-blue-700', safety: 'bg-green-50 text-green-700', repair: 'bg-amber-50 text-amber-700', equipment: 'bg-purple-50 text-purple-700' };
|
||||
// 카테고리 — API 동적 로드
|
||||
let _categories = null;
|
||||
async function loadCategories() {
|
||||
if (_categories) return _categories;
|
||||
try { const r = await api('/consumable-categories'); _categories = r.data || []; } catch(e) { _categories = []; }
|
||||
return _categories;
|
||||
}
|
||||
function getCatLabel(code) { return (_categories || []).find(c => c.category_code === code)?.category_name || code || '-'; }
|
||||
function getCatIcon(code) { return (_categories || []).find(c => c.category_code === code)?.icon || 'fa-box'; }
|
||||
function getCatBgClass(code) {
|
||||
const c = (_categories || []).find(x => x.category_code === code);
|
||||
if (!c) return 'bg-gray-50 text-gray-700';
|
||||
// Tailwind class 생성 (인라인 스타일 대신)
|
||||
return '';
|
||||
}
|
||||
function getCatColors(code) {
|
||||
const c = (_categories || []).find(x => x.category_code === code);
|
||||
return c ? { bg: c.color_bg, fg: c.color_fg } : { bg: '#f3f4f6', fg: '#374151' };
|
||||
}
|
||||
const STATUS_LABELS = { received: '입고완료', returned: '반품' };
|
||||
const STATUS_COLORS = { received: 'badge-teal', returned: 'badge-red' };
|
||||
|
||||
@@ -69,20 +85,17 @@ async function loadReceivedBasis() {
|
||||
/* ===== 렌더링 함수들 ===== */
|
||||
function renderCategorySummary(data) {
|
||||
const el = document.getElementById('paCategorySummary');
|
||||
const allCategories = ['consumable', 'safety', 'repair', 'equipment'];
|
||||
const cats = _categories || [];
|
||||
const dataMap = {};
|
||||
data.forEach(d => { dataMap[d.category] = d; });
|
||||
const totalAmount = data.reduce((sum, d) => sum + Number(d.total_amount || 0), 0);
|
||||
|
||||
el.innerHTML = allCategories.map(cat => {
|
||||
const d = dataMap[cat] || { count: 0, total_amount: 0 };
|
||||
const label = CAT_LABELS[cat];
|
||||
const icon = CAT_ICONS[cat];
|
||||
const bg = CAT_BG[cat];
|
||||
el.innerHTML = cats.map(cat => {
|
||||
const d = dataMap[cat.category_code] || { count: 0, total_amount: 0 };
|
||||
return `<div class="bg-white rounded-xl shadow-sm p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-8 h-8 rounded-lg ${bg} flex items-center justify-center"><i class="fas ${icon} text-sm"></i></div>
|
||||
<span class="text-sm font-medium text-gray-700">${label}</span>
|
||||
<div class="w-8 h-8 rounded-lg flex items-center justify-center" style="background:${cat.color_bg};color:${cat.color_fg}"><i class="fas ${cat.icon} text-sm"></i></div>
|
||||
<span class="text-sm font-medium text-gray-700">${escapeHtml(cat.category_name)}</span>
|
||||
</div>
|
||||
<div class="text-xl font-bold text-gray-800">${Number(d.total_amount || 0).toLocaleString()}<span class="text-xs font-normal text-gray-400 ml-1">원</span></div>
|
||||
<div class="text-xs text-gray-500 mt-1">${d.count || 0}건</div>
|
||||
@@ -127,8 +140,8 @@ function renderPurchaseList(data) {
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = data.map(p => {
|
||||
const catLabel = CAT_LABELS[p.category] || p.category;
|
||||
const catColor = CAT_BG[p.category] || '';
|
||||
const catLabel = getCatLabel(p.category);
|
||||
const catColor = getCatBgClass(p.category);
|
||||
const subtotal = (p.quantity || 0) * (p.unit_price || 0);
|
||||
const basePrice = Number(p.base_price || 0);
|
||||
const unitPrice = Number(p.unit_price || 0);
|
||||
@@ -190,8 +203,8 @@ function renderReceivedList(data) {
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = data.map(r => {
|
||||
const catLabel = CAT_LABELS[r.category] || r.category || '-';
|
||||
const catColor = CAT_BG[r.category] || '';
|
||||
const catLabel = getCatLabel(r.category);
|
||||
const catColor = getCatBgClass(r.category);
|
||||
const statusLabel = STATUS_LABELS[r.status] || r.status;
|
||||
const statusColor = STATUS_COLORS[r.status] || 'badge-gray';
|
||||
return `<tr class="hover:bg-gray-50 ${r.status === 'returned' ? 'bg-red-50' : ''}">
|
||||
@@ -236,6 +249,7 @@ async function cancelSettlement(vendorId) {
|
||||
/* ===== Init ===== */
|
||||
(async function() {
|
||||
if (!await initAuth()) return;
|
||||
await loadCategories();
|
||||
const now = new Date();
|
||||
document.getElementById('paMonth').value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user