/* ===== 구매 분석 페이지 ===== */ // 카테고리 — 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' }; let currentYearMonth = ''; let dateBasis = 'purchase'; // 'purchase' 또는 'received' function setDateBasis(basis) { dateBasis = basis; document.getElementById('btnDatePurchase').className = basis === 'purchase' ? 'px-3 py-2 bg-orange-600 text-white' : 'px-3 py-2 bg-white text-gray-600 hover:bg-gray-50'; document.getElementById('btnDateReceived').className = basis === 'received' ? 'px-3 py-2 bg-orange-600 text-white' : 'px-3 py-2 bg-white text-gray-600 hover:bg-gray-50'; } async function loadAnalysis() { currentYearMonth = document.getElementById('paMonth').value; if (!currentYearMonth) { showToast('월을 선택해주세요.', 'error'); return; } try { if (dateBasis === 'purchase') { await loadPurchaseBasis(); } else { await loadReceivedBasis(); } } catch (e) { showToast('데이터 로드 실패: ' + e.message, 'error'); } } /* ===== 구매일 기준 (기존) ===== */ async function loadPurchaseBasis() { // 입고 섹션 숨김 document.getElementById('paReceivedSection').classList.add('hidden'); const [summaryRes, purchasesRes, priceChangesRes] = await Promise.all([ api(`/settlements/summary?year_month=${currentYearMonth}`), api(`/settlements/purchases?year_month=${currentYearMonth}`), api(`/settlements/price-changes?year_month=${currentYearMonth}`) ]); renderCategorySummary(summaryRes.data?.categorySummary || []); renderVendorSummary(summaryRes.data?.vendorSummary || []); renderPurchaseList(purchasesRes.data || []); renderPriceChanges(priceChangesRes.data || []); } /* ===== 입고일 기준 ===== */ async function loadReceivedBasis() { const [summaryRes, listRes] = await Promise.all([ api(`/settlements/received-summary?year_month=${currentYearMonth}`), api(`/settlements/received-list?year_month=${currentYearMonth}`) ]); renderCategorySummary(summaryRes.data?.categorySummary || []); // 업체/구매목록/가격변동은 빈 상태로 document.getElementById('paVendorSummary').innerHTML = '입고일 기준에서는 업체별 정산이 표시되지 않습니다.'; document.getElementById('paPurchaseList').innerHTML = '아래 입고 내역을 확인하세요.'; document.getElementById('paPriceChanges').innerHTML = ''; // 입고 섹션 표시 document.getElementById('paReceivedSection').classList.remove('hidden'); renderReceivedList(listRes.data || []); } /* ===== 렌더링 함수들 ===== */ function renderCategorySummary(data) { const el = document.getElementById('paCategorySummary'); 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 = cats.map(cat => { const d = dataMap[cat.category_code] || { count: 0, total_amount: 0 }; return `
${escapeHtml(cat.category_name)}
${Number(d.total_amount || 0).toLocaleString()}
${d.count || 0}건
`; }).join('') + `
${dateBasis === 'purchase' ? '구매' : '입고'} 월 합계: ${totalAmount.toLocaleString()}원
`; } function renderVendorSummary(data) { const tbody = document.getElementById('paVendorSummary'); if (!data.length) { tbody.innerHTML = '해당 월 구매 내역이 없습니다.'; return; } tbody.innerHTML = data.map(v => { const isCompleted = v.settlement_status === 'completed'; const statusBadge = isCompleted ? '정산완료' : '미정산'; const vendorName = v.vendor_name || '(업체 미지정)'; const vendorId = v.vendor_id || 0; let actionBtn = ''; if (vendorId > 0) { actionBtn = isCompleted ? `` : ``; } return ` ${escapeHtml(vendorName)} ${v.count}건 ${Number(v.total_amount || 0).toLocaleString()}원 ${statusBadge} ${actionBtn} `; }).join(''); } function renderPurchaseList(data) { const tbody = document.getElementById('paPurchaseList'); if (!data.length) { tbody.innerHTML = '해당 월 구매 내역이 없습니다.'; return; } tbody.innerHTML = data.map(p => { 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); const hasPriceDiff = basePrice > 0 && unitPrice > 0 && basePrice !== unitPrice; const priceDiffClass = hasPriceDiff ? (unitPrice > basePrice ? 'text-red-600 font-semibold' : 'text-blue-600 font-semibold') : ''; return `
${escapeHtml(p.item_name)}${p.spec ? ' [' + escapeHtml(p.spec) + ']' : ''}
${escapeHtml(p.maker || '')}
${catLabel} ${p.quantity} ${unitPrice.toLocaleString()}원${hasPriceDiff ? `
(기준: ${basePrice.toLocaleString()})
` : ''} ${subtotal.toLocaleString()}원 ${escapeHtml(p.vendor_name || '-')} ${formatDate(p.purchase_date)} ${escapeHtml(p.notes || '')} `; }).join(''); } function renderPriceChanges(data) { const el = document.getElementById('paPriceChanges'); if (!data.length) { el.innerHTML = '

가격 변동 항목이 없습니다.

'; return; } el.innerHTML = `${data.map(p => { const diff = Number(p.unit_price) - Number(p.base_price); const arrow = diff > 0 ? '▲' : '▼'; const color = diff > 0 ? 'text-red-600' : 'text-blue-600'; return ``; }).join('')}
품목 기준가 실구매가 차이 업체 구매일
${escapeHtml(p.item_name)}${p.spec ? ' [' + escapeHtml(p.spec) + ']' : ''} ${p.maker ? '(' + escapeHtml(p.maker) + ')' : ''} ${Number(p.base_price).toLocaleString()}원 ${Number(p.unit_price).toLocaleString()}원 ${arrow} ${Math.abs(diff).toLocaleString()}원 ${escapeHtml(p.vendor_name || '-')} ${formatDate(p.purchase_date)}
`; } function renderReceivedList(data) { const tbody = document.getElementById('paReceivedList'); if (!data.length) { tbody.innerHTML = '해당 월 입고 내역이 없습니다.'; return; } tbody.innerHTML = data.map(r => { 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 `
${escapeHtml(r.item_name || '-')}${r.spec ? ' [' + escapeHtml(r.spec) + ']' : ''}
${escapeHtml(r.maker || '')} · ${escapeHtml(r.requester_name || '')}
${catLabel} ${r.quantity} ${r.unit_price ? Number(r.unit_price).toLocaleString() + '원' : '-'} ${escapeHtml(r.vendor_name || '-')} ${formatDateTime(r.received_at)} ${escapeHtml(r.received_location || '-')} ${statusLabel} `; }).join(''); } /* ===== 정산 처리 ===== */ async function completeSettlement(vendorId) { if (!confirm('이 업체의 정산을 완료 처리하시겠습니까?')) return; try { await api('/settlements/complete', { method: 'POST', body: JSON.stringify({ year_month: currentYearMonth, vendor_id: vendorId }) }); showToast('정산 완료 처리되었습니다.'); await loadAnalysis(); } catch (e) { showToast(e.message, 'error'); } } async function cancelSettlement(vendorId) { if (!confirm('정산 완료를 취소하시겠습니까?')) return; try { await api('/settlements/cancel', { method: 'POST', body: JSON.stringify({ year_month: currentYearMonth, vendor_id: vendorId }) }); showToast('정산이 취소되었습니다.'); await loadAnalysis(); } catch (e) { showToast(e.message, 'error'); } } /* ===== 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')}`; })();