feat: 도매상 잔고 모달에 월간 매출 추가

- 백제/지오영/수인 월간매출 API 라우트 추가
- 모달 UI: 잔고 + 월간 매출 동시 표시
- 총 주문액 / 총 미수금 요약 표시
This commit is contained in:
thug0bin
2026-03-06 18:01:37 +09:00
parent 4b2d934839
commit 5519f5ae62
9 changed files with 562 additions and 33 deletions

View File

@@ -2256,26 +2256,43 @@
text-transform: uppercase;
letter-spacing: 0.5px;
}
.balance-total {
.balance-summary {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(6, 182, 212, 0.1));
border-radius: 12px;
padding: 20px;
text-align: center;
margin-top: 8px;
display: flex;
justify-content: space-around;
align-items: center;
margin-top: 12px;
gap: 16px;
}
.balance-total-label {
.summary-item {
text-align: center;
flex: 1;
}
.summary-label {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 8px;
}
.balance-total-value {
font-size: 28px;
.summary-value {
font-size: 24px;
font-weight: 700;
font-family: 'JetBrains Mono', monospace;
}
.summary-value.sales {
color: #10b981;
}
.summary-value.balance {
background: linear-gradient(135deg, #a855f7, #06b6d4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.summary-divider {
width: 1px;
height: 50px;
background: var(--border);
}
.balance-updated {
text-align: center;
font-size: 11px;
@@ -2300,17 +2317,23 @@
baekje: {
id: 'baekje', name: '백제약품', icon: '💉',
logo: '/static/img/logo_baekje.svg',
color: '#f59e0b', api: '/api/baekje/balance'
color: '#f59e0b',
balanceApi: '/api/baekje/balance',
salesApi: '/api/baekje/monthly-sales'
},
geoyoung: {
id: 'geoyoung', name: '지오영', icon: '🏭',
logo: '/static/img/logo_geoyoung.ico',
color: '#06b6d4', api: '/api/geoyoung/balance'
color: '#06b6d4',
balanceApi: '/api/geoyoung/balance',
salesApi: '/api/geoyoung/monthly-sales'
},
sooin: {
id: 'sooin', name: '수인약품', icon: '💊',
logo: '/static/img/logo_sooin.svg',
color: '#a855f7', api: '/api/sooin/balance'
color: '#a855f7',
balanceApi: '/api/sooin/balance',
salesApi: '/api/sooin/monthly-sales'
}
};
const WHOLESALER_ORDER = ['baekje', 'geoyoung', 'sooin'];
@@ -2320,36 +2343,47 @@
content.innerHTML = `
<div class="loading-state">
<div class="loading-spinner"></div>
<div>잔고 조회 중...</div>
<div>잔고 및 매출 조회 중...</div>
</div>`;
const wholesalers = WHOLESALER_ORDER.map(id => WHOLESALER_CONFIG[id]);
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const results = {};
const balanceResults = {};
const salesResults = {};
let totalBalance = 0;
let totalSales = 0;
// 병렬로 조회
await Promise.all(wholesalers.map(async (ws) => {
try {
const resp = await fetch(ws.api, { timeout: 30000 });
const data = await resp.json();
results[ws.id] = data;
if (data.success && data.balance) {
totalBalance += data.balance;
}
} catch (err) {
results[ws.id] = { success: false, error: err.message };
}
}));
// 병렬로 잔고 + 월간매출 조회
await Promise.all(wholesalers.flatMap(ws => [
// 잔고 조회
fetch(ws.balanceApi).then(r => r.json()).then(data => {
balanceResults[ws.id] = data;
if (data.success && data.balance) totalBalance += data.balance;
}).catch(err => {
balanceResults[ws.id] = { success: false, error: err.message };
}),
// 월간 매출 조회
fetch(`${ws.salesApi}?year=${year}&month=${month}`).then(r => r.json()).then(data => {
salesResults[ws.id] = data;
if (data.success && data.total_amount) totalSales += data.total_amount;
}).catch(err => {
salesResults[ws.id] = { success: false, error: err.message };
})
]));
// 결과 렌더링
let html = '<div class="balance-grid">';
wholesalers.forEach(ws => {
const data = results[ws.id];
const isError = !data.success;
const balance = data.balance || 0;
const prevBalance = data.prev_balance || data.prev_month_balance || 0;
const balData = balanceResults[ws.id] || {};
const salesData = salesResults[ws.id] || {};
const isError = !balData.success;
const balance = balData.balance || 0;
const monthlySales = salesData.total_amount || 0;
const monthlyPaid = salesData.total_paid || 0;
html += `
<div class="balance-card ${isError ? 'error' : ''}">
@@ -2362,8 +2396,9 @@
<div class="balance-name">${ws.name}</div>
<div class="balance-detail">
${isError
? `${data.error || '조회 실패'}`
: `전월/전일: ${prevBalance.toLocaleString()}`}
? `${balData.error || '조회 실패'}`
: `${month}월 매출: <span style="color:${ws.color};font-weight:600;">${monthlySales.toLocaleString()}</span>
${monthlyPaid > 0 ? ` · 입금: ${monthlyPaid.toLocaleString()}` : ''}`}
</div>
</div>
<div class="balance-amount">
@@ -2376,9 +2411,16 @@
});
html += `
<div class="balance-total">
<div class="balance-total-label">총 미수금</div>
<div class="balance-total-value">${totalBalance.toLocaleString()}</div>
<div class="balance-summary">
<div class="summary-item">
<div class="summary-label">📊 ${month}월 총 주문</div>
<div class="summary-value sales">${totalSales.toLocaleString()}원</div>
</div>
<div class="summary-divider"></div>
<div class="summary-item">
<div class="summary-label">💰 총 미수금</div>
<div class="summary-value balance">${totalBalance.toLocaleString()}원</div>
</div>
</div>
<div class="balance-updated">🕐 ${new Date().toLocaleString('ko-KR')}</div>
</div>`;