feat: 주문 모달 한도/매출 표시 및 UI 개선

- 도매상 한도 API 추가 (wholesaler_limits 테이블)
- 다중 도매상 모달: 월 한도 + 실제 월 매출 표시
- 주문 후 예상 사용량 계산 및 경고 표시
- 이모지 대신 로고 이미지 사용
- 약가 → 매출액 헤더 변경
- 매출액 계산: SUM(DRUPRICE)
This commit is contained in:
thug0bin
2026-03-07 00:24:32 +09:00
parent 29597d55fa
commit 846883cbfa
3 changed files with 200 additions and 6 deletions

View File

@@ -774,7 +774,7 @@
<div class="stat-card emerald">
<div class="stat-icon">💰</div>
<div class="stat-value" id="statTotalAmount">-</div>
<div class="stat-label">약가</div>
<div class="stat-label">매출액</div>
</div>
<div class="stat-card orange">
<div class="stat-icon">🛒</div>
@@ -807,7 +807,7 @@
<th class="center">현재고</th>
<th class="center">처방횟수</th>
<th class="center">투약량</th>
<th class="right">약가</th>
<th class="right">매출액</th>
<th class="center" style="width:90px">주문수량</th>
</tr>
</thead>
@@ -1156,14 +1156,55 @@
// 다중 도매상 선택을 위한 전역 변수
let pendingWholesalerItems = {};
let pendingOtherItems = [];
let wholesalerLimits = {}; // 도매상 한도 캐시
function openWholesalerSelectModal(itemsByWholesaler, otherItems) {
async function openWholesalerSelectModal(itemsByWholesaler, otherItems) {
pendingWholesalerItems = itemsByWholesaler;
pendingOtherItems = otherItems;
const modal = document.getElementById('multiWholesalerModal');
const body = document.getElementById('multiWholesalerBody');
// 로딩 표시
body.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-muted);">📊 한도 및 월 매출 조회 중...</div>';
modal.classList.add('show');
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
// 1. 도매상 한도 정보 가져오기
try {
const res = await fetch('/api/order/wholesaler/limits');
const data = await res.json();
if (data.success) {
data.limits.forEach(l => {
wholesalerLimits[l.wholesaler_id] = l;
});
}
} catch (e) {
console.warn('한도 조회 실패:', e);
}
// 2. 실제 월 매출 가져오기 (도매상 API 호출)
const wholesalerConfigs = WHOLESALER_ORDER.map(id => WHOLESALER_CONFIG[id]);
await Promise.all(wholesalerConfigs.map(async ws => {
try {
const salesRes = await fetch(`${ws.salesApi}?year=${year}&month=${month}`);
const salesData = await salesRes.json();
if (salesData.success && wholesalerLimits[ws.id]) {
// 실제 월 매출로 current_usage 업데이트
wholesalerLimits[ws.id].current_usage = salesData.total_amount || 0;
wholesalerLimits[ws.id].usage_percent = wholesalerLimits[ws.id].monthly_limit > 0
? Math.round((salesData.total_amount || 0) / wholesalerLimits[ws.id].monthly_limit * 1000) / 10
: 0;
wholesalerLimits[ws.id].remaining = wholesalerLimits[ws.id].monthly_limit - (salesData.total_amount || 0);
}
} catch (e) {
console.warn(`${ws.id} 월매출 조회 실패:`, e);
}
}));
const wsIds = Object.keys(itemsByWholesaler);
// 전체 총액 계산
@@ -1190,14 +1231,44 @@
wsIds.forEach(wsId => {
const ws = WHOLESALERS[wsId];
const items = itemsByWholesaler[wsId];
const limit = wholesalerLimits[wsId];
// 도매상별 소계
const wsTotal = items.reduce((sum, item) => sum + (item.unit_price || 0) * item.qty, 0);
// 한도 정보 계산
let limitHtml = '';
if (limit) {
const afterOrder = limit.current_usage + wsTotal;
const afterPercent = (afterOrder / limit.monthly_limit * 100).toFixed(1);
const isOver = afterOrder > limit.monthly_limit;
const isWarning = afterPercent >= (limit.warning_threshold * 100);
limitHtml = `
<div style="margin-top:8px;padding:8px 12px;background:var(--bg-tertiary);border-radius:6px;font-size:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:4px;">
<span>월 한도</span>
<span style="font-family:'JetBrains Mono',monospace;">${(limit.monthly_limit/10000).toLocaleString()}만원</span>
</div>
<div style="display:flex;justify-content:space-between;margin-bottom:4px;">
<span>이번달 사용</span>
<span style="font-family:'JetBrains Mono',monospace;">${(limit.current_usage/10000).toLocaleString()}만원 (${limit.usage_percent}%)</span>
</div>
<div style="display:flex;justify-content:space-between;color:${isOver ? 'var(--accent-red)' : isWarning ? 'var(--accent-amber)' : 'var(--accent-emerald)'};">
<span>주문 후</span>
<span style="font-family:'JetBrains Mono',monospace;font-weight:600;">
${(afterOrder/10000).toLocaleString()}만원 (${afterPercent}%)
${isOver ? ' ⚠️ 초과!' : isWarning ? ' ⚠️' : ' ✓'}
</span>
</div>
</div>
`;
}
html += `
<div class="multi-ws-card ${wsId}">
<div class="multi-ws-header">
<span class="multi-ws-icon">${ws.icon}</span>
<img src="${ws.logo}" alt="${ws.name}" style="width:24px;height:24px;object-fit:contain;margin-right:8px;">
<span class="multi-ws-name">${ws.name}</span>
<span class="multi-ws-count">${items.length}개 품목</span>
${wsTotal > 0 ? `<span style="margin-left:auto;margin-right:12px;font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--accent-cyan);">₩${wsTotal.toLocaleString()}</span>` : ''}
@@ -1213,6 +1284,7 @@
}).join('')}
${items.length > 3 ? `<div class="multi-ws-item more">... 외 ${items.length - 3}개</div>` : ''}
</div>
${limitHtml}
</div>
`;
});
@@ -1446,7 +1518,7 @@
const headerDiv = modal.querySelector('.order-modal-header');
// 도매상별 헤더 및 본문 텍스트 변경
document.getElementById('orderConfirmTitle').innerHTML = `${ws.icon} ${ws.name} 주문 확인`;
document.getElementById('orderConfirmTitle').innerHTML = `<img src="${ws.logo}" alt="${ws.name}" style="width:24px;height:24px;object-fit:contain;vertical-align:middle;margin-right:8px;">${ws.name} 주문 확인`;
document.getElementById('orderConfirmWholesaler').textContent = ws.name;
headerDiv.style.background = ws.gradient;