feat: 더블클릭 시 지오영+수인 동시 재고 조회
- openWholesaleModal: 두 도매상 동시 API 호출 - Promise.all로 병렬 조회 (빠른 응답) - 도매상별 섹션 구분 UI (지오영: 청록, 수인: 보라) - 각 도매상별 담기 버튼 - 수인은 단가 정보도 표시
This commit is contained in:
parent
90d993156e
commit
19c70e42fb
@ -1279,14 +1279,15 @@
|
||||
if (e.key === 'Enter') loadUsageData();
|
||||
});
|
||||
|
||||
// ──────────────── 지오영 재고 조회 ────────────────
|
||||
let currentGeoyoungItem = null;
|
||||
// ──────────────── 도매상 재고 조회 (지오영 + 수인) ────────────────
|
||||
let currentWholesaleItem = null;
|
||||
window.wholesaleItems = { geoyoung: [], sooin: [] };
|
||||
|
||||
function openGeoyoungModal(idx) {
|
||||
function openWholesaleModal(idx) {
|
||||
const item = usageData[idx];
|
||||
if (!item) return;
|
||||
|
||||
currentGeoyoungItem = item;
|
||||
currentWholesaleItem = item;
|
||||
|
||||
// 모달 열기
|
||||
document.getElementById('geoModalProductName').textContent = item.product_name;
|
||||
@ -1300,142 +1301,193 @@
|
||||
document.getElementById('geoResultBody').innerHTML = `
|
||||
<div class="geo-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div>지오영 재고 조회 중...</div>
|
||||
<div>도매상 재고 조회 중... (지오영 + 수인)</div>
|
||||
</div>`;
|
||||
document.getElementById('geoSearchKeyword').style.display = 'none';
|
||||
|
||||
// API 호출 (보험코드로 먼저 시도)
|
||||
searchGeoyoung(item.drug_code, item.product_name);
|
||||
// 두 도매상 동시 호출
|
||||
searchAllWholesalers(item.drug_code, item.product_name);
|
||||
}
|
||||
|
||||
function closeGeoyoungModal() {
|
||||
document.getElementById('geoyoungModal').classList.remove('show');
|
||||
currentGeoyoungItem = null;
|
||||
currentWholesaleItem = null;
|
||||
}
|
||||
|
||||
async function searchGeoyoung(kdCode, productName) {
|
||||
async function searchAllWholesalers(kdCode, productName) {
|
||||
const resultBody = document.getElementById('geoResultBody');
|
||||
|
||||
// 두 도매상 동시 호출
|
||||
const [geoResult, sooinResult] = await Promise.all([
|
||||
searchGeoyoungAPI(kdCode, productName),
|
||||
searchSooinAPI(kdCode)
|
||||
]);
|
||||
|
||||
// 결과 저장
|
||||
window.wholesaleItems = {
|
||||
geoyoung: geoResult.items || [],
|
||||
sooin: sooinResult.items || []
|
||||
};
|
||||
|
||||
// 통합 렌더링
|
||||
renderWholesaleResults(geoResult, sooinResult);
|
||||
}
|
||||
|
||||
async function searchGeoyoungAPI(kdCode, productName) {
|
||||
try {
|
||||
// 1차: 보험코드(KD코드)로 검색
|
||||
// 1차: 보험코드로 검색
|
||||
let response = await fetch(`/api/geoyoung/stock?kd_code=${encodeURIComponent(kdCode)}`);
|
||||
let data = await response.json();
|
||||
|
||||
// 결과 없으면 성분명으로 재검색
|
||||
if (data.success && data.count === 0) {
|
||||
document.getElementById('geoResultBody').innerHTML = `
|
||||
<div class="geo-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div>성분명으로 재검색 중...</div>
|
||||
</div>`;
|
||||
|
||||
response = await fetch(`/api/geoyoung/stock-by-name?product_name=${encodeURIComponent(productName)}`);
|
||||
data = await response.json();
|
||||
}
|
||||
|
||||
if (!data.success) {
|
||||
resultBody.innerHTML = `
|
||||
<div class="geo-error">
|
||||
<div>❌ ${data.message || '조회 실패'}</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.count === 0) {
|
||||
resultBody.innerHTML = `
|
||||
<div class="geo-empty">
|
||||
<div>📭 지오영에 해당 제품이 없습니다</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 검색어 표시
|
||||
if (data.extracted_ingredient) {
|
||||
document.getElementById('geoSearchKeyword').textContent = `검색: "${data.extracted_ingredient}"`;
|
||||
document.getElementById('geoSearchKeyword').style.display = 'block';
|
||||
}
|
||||
|
||||
// 결과 렌더링
|
||||
renderGeoyoungResults(data.items);
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
resultBody.innerHTML = `
|
||||
<div class="geo-error">
|
||||
<div>❌ 네트워크 오류: ${err.message}</div>
|
||||
</div>`;
|
||||
return { success: false, error: err.message, items: [] };
|
||||
}
|
||||
}
|
||||
|
||||
async function searchSooinAPI(kdCode) {
|
||||
try {
|
||||
const response = await fetch(`/api/sooin/stock?keyword=${encodeURIComponent(kdCode)}`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message, items: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function renderGeoyoungResults(items) {
|
||||
function renderWholesaleResults(geoResult, sooinResult) {
|
||||
const resultBody = document.getElementById('geoResultBody');
|
||||
|
||||
const geoItems = geoResult.items || [];
|
||||
const sooinItems = sooinResult.items || [];
|
||||
|
||||
// 재고 있는 것 먼저 정렬
|
||||
items.sort((a, b) => (b.stock > 0 ? 1 : 0) - (a.stock > 0 ? 1 : 0) || b.stock - a.stock);
|
||||
geoItems.sort((a, b) => (b.stock > 0 ? 1 : 0) - (a.stock > 0 ? 1 : 0) || b.stock - a.stock);
|
||||
sooinItems.sort((a, b) => (b.stock > 0 ? 1 : 0) - (a.stock > 0 ? 1 : 0) || b.stock - a.stock);
|
||||
|
||||
let html = `<table class="geo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>제품명</th>
|
||||
<th>규격</th>
|
||||
<th>재고</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>`;
|
||||
let html = '';
|
||||
|
||||
items.forEach((item, idx) => {
|
||||
const hasStock = item.stock > 0;
|
||||
html += `
|
||||
<tr class="${hasStock ? '' : 'no-stock'}">
|
||||
<td>
|
||||
<div class="geo-product">
|
||||
<span class="geo-name">${escapeHtml(item.product_name)}</span>
|
||||
<span class="geo-code">${item.insurance_code}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="geo-spec">${item.specification}</td>
|
||||
<td class="geo-stock ${hasStock ? 'in-stock' : 'out-stock'}">${item.stock}</td>
|
||||
<td>
|
||||
${hasStock ? `<button class="geo-add-btn" onclick="addGeoyoungToCart(${idx})">담기</button>` : ''}
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
// ═══════ 지오영 섹션 ═══════
|
||||
html += `<div class="ws-section">
|
||||
<div class="ws-header geoyoung">
|
||||
<span class="ws-icon">🏭</span>
|
||||
<span class="ws-name">지오영</span>
|
||||
<span class="ws-count">${geoItems.length}건</span>
|
||||
</div>`;
|
||||
|
||||
html += '</tbody></table>';
|
||||
if (geoItems.length > 0) {
|
||||
html += `<table class="geo-table">
|
||||
<thead><tr><th>제품명</th><th>규격</th><th>재고</th><th></th></tr></thead>
|
||||
<tbody>`;
|
||||
|
||||
geoItems.forEach((item, idx) => {
|
||||
const hasStock = item.stock > 0;
|
||||
html += `
|
||||
<tr class="${hasStock ? '' : 'no-stock'}">
|
||||
<td>
|
||||
<div class="geo-product">
|
||||
<span class="geo-name">${escapeHtml(item.product_name)}</span>
|
||||
<span class="geo-code">${item.insurance_code}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="geo-spec">${item.specification}</td>
|
||||
<td class="geo-stock ${hasStock ? 'in-stock' : 'out-stock'}">${item.stock}</td>
|
||||
<td>${hasStock ? `<button class="geo-add-btn" onclick="addToCartFromWholesale('geoyoung', ${idx})">담기</button>` : ''}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
html += '</tbody></table>';
|
||||
} else {
|
||||
html += `<div class="ws-empty">📭 검색 결과 없음</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// 전역에 저장 (담기용)
|
||||
window.geoyoungItems = items;
|
||||
// ═══════ 수인약품 섹션 ═══════
|
||||
html += `<div class="ws-section">
|
||||
<div class="ws-header sooin">
|
||||
<span class="ws-icon">💊</span>
|
||||
<span class="ws-name">수인약품</span>
|
||||
<span class="ws-count">${sooinItems.length}건</span>
|
||||
</div>`;
|
||||
|
||||
if (sooinItems.length > 0) {
|
||||
html += `<table class="geo-table">
|
||||
<thead><tr><th>제품명</th><th>규격</th><th>단가</th><th>재고</th><th></th></tr></thead>
|
||||
<tbody>`;
|
||||
|
||||
sooinItems.forEach((item, idx) => {
|
||||
const hasStock = item.stock > 0;
|
||||
html += `
|
||||
<tr class="${hasStock ? '' : 'no-stock'}">
|
||||
<td>
|
||||
<div class="geo-product">
|
||||
<span class="geo-name">${escapeHtml(item.name)}</span>
|
||||
<span class="geo-code">${item.code} · ${item.manufacturer || ''}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="geo-spec">${item.spec || '-'}</td>
|
||||
<td class="geo-price">${item.price ? item.price.toLocaleString() + '원' : '-'}</td>
|
||||
<td class="geo-stock ${hasStock ? 'in-stock' : 'out-stock'}">${item.stock}</td>
|
||||
<td>${hasStock ? `<button class="geo-add-btn sooin" onclick="addToCartFromWholesale('sooin', ${idx})">담기</button>` : ''}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
html += '</tbody></table>';
|
||||
} else {
|
||||
html += `<div class="ws-empty">📭 검색 결과 없음</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
resultBody.innerHTML = html;
|
||||
}
|
||||
|
||||
function addGeoyoungToCart(idx) {
|
||||
const item = window.geoyoungItems[idx];
|
||||
if (!item || !currentGeoyoungItem) return;
|
||||
function addToCartFromWholesale(wholesaler, idx) {
|
||||
const items = window.wholesaleItems[wholesaler];
|
||||
const item = items[idx];
|
||||
if (!item || !currentWholesaleItem) return;
|
||||
|
||||
// 수량 계산 (규격에서 숫자 추출)
|
||||
const specMatch = item.specification.match(/(\d+)/);
|
||||
// 규격에서 수량 추출
|
||||
const spec = wholesaler === 'geoyoung' ? item.specification : (item.spec || '');
|
||||
const specMatch = spec.match(/(\d+)/);
|
||||
const specQty = specMatch ? parseInt(specMatch[1]) : 1;
|
||||
|
||||
// 필요 수량 계산
|
||||
const needed = currentGeoyoungItem.total_dose;
|
||||
const needed = currentWholesaleItem.total_dose;
|
||||
const suggestedQty = Math.ceil(needed / specQty);
|
||||
|
||||
const qty = prompt(`주문 수량 (${item.specification} 기준)\n\n필요량: ${needed}개\n규격: ${specQty}개/단위\n추천: ${suggestedQty}단위 (${suggestedQty * specQty}개)`, suggestedQty);
|
||||
const supplierName = wholesaler === 'geoyoung' ? '지오영' : '수인약품';
|
||||
const productName = wholesaler === 'geoyoung' ? item.product_name : item.name;
|
||||
|
||||
const qty = prompt(`[${supplierName}] 주문 수량 (${spec} 기준)\n\n필요량: ${needed}개\n규격: ${specQty}개/단위\n추천: ${suggestedQty}단위 (${suggestedQty * specQty}개)`, suggestedQty);
|
||||
|
||||
if (!qty || isNaN(qty)) return;
|
||||
|
||||
// 장바구니에 추가 (지오영 정보 포함)
|
||||
// 장바구니에 추가
|
||||
const cartItem = {
|
||||
drug_code: currentGeoyoungItem.drug_code,
|
||||
product_name: item.product_name,
|
||||
supplier: '지오영',
|
||||
drug_code: currentWholesaleItem.drug_code,
|
||||
product_name: productName,
|
||||
supplier: supplierName,
|
||||
qty: parseInt(qty),
|
||||
specification: item.specification,
|
||||
geoyoung_code: item.insurance_code
|
||||
specification: spec,
|
||||
wholesaler: wholesaler,
|
||||
internal_code: wholesaler === 'geoyoung' ? item.internal_code : item.internal_code,
|
||||
geoyoung_code: wholesaler === 'geoyoung' ? item.insurance_code : null,
|
||||
sooin_code: wholesaler === 'sooin' ? item.code : null
|
||||
};
|
||||
|
||||
// 기존 항목 체크
|
||||
const existing = cart.find(c => c.drug_code === currentGeoyoungItem.drug_code && c.specification === item.specification);
|
||||
// 기존 항목 체크 (같은 도매상 + 같은 규격)
|
||||
const existing = cart.find(c =>
|
||||
c.drug_code === currentWholesaleItem.drug_code &&
|
||||
c.specification === spec &&
|
||||
c.wholesaler === wholesaler
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
existing.qty = parseInt(qty);
|
||||
} else {
|
||||
@ -1444,24 +1496,28 @@
|
||||
|
||||
updateCartUI();
|
||||
closeGeoyoungModal();
|
||||
showToast(`✅ ${item.product_name} (${item.specification}) ${qty}개 추가`, 'success');
|
||||
showToast(`✅ [${supplierName}] ${productName} (${spec}) ${qty}개 추가`, 'success');
|
||||
}
|
||||
|
||||
// 테이블 행 더블클릭으로 지오영 모달 열기
|
||||
// 하위 호환 (기존 함수명 유지)
|
||||
function openGeoyoungModal(idx) { openWholesaleModal(idx); }
|
||||
function addGeoyoungToCart(idx) { addToCartFromWholesale('geoyoung', idx); }
|
||||
|
||||
// 테이블 행 더블클릭으로 도매상 모달 열기
|
||||
document.addEventListener('dblclick', function(e) {
|
||||
const row = e.target.closest('tr[data-idx]');
|
||||
if (row) {
|
||||
const idx = parseInt(row.dataset.idx);
|
||||
openGeoyoungModal(idx);
|
||||
openWholesaleModal(idx);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 지오영 재고 조회 모달 -->
|
||||
<!-- 도매상 재고 조회 모달 (지오영 + 수인) -->
|
||||
<div class="geo-modal" id="geoyoungModal">
|
||||
<div class="geo-modal-content">
|
||||
<div class="geo-modal-header">
|
||||
<h3>🏭 지오영 재고 조회</h3>
|
||||
<h3>📦 도매상 재고 조회</h3>
|
||||
<button class="geo-close" onclick="closeGeoyoungModal()">✕</button>
|
||||
</div>
|
||||
<div class="geo-modal-info">
|
||||
@ -1649,6 +1705,62 @@
|
||||
background: #0891b2;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.geo-add-btn.sooin {
|
||||
background: var(--accent-purple);
|
||||
}
|
||||
.geo-add-btn.sooin:hover {
|
||||
background: #7c3aed;
|
||||
}
|
||||
.geo-price {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 도매상 섹션 구분 */
|
||||
.ws-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ws-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ws-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.ws-header.geoyoung {
|
||||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.2), rgba(8, 145, 178, 0.1));
|
||||
border-left: 3px solid var(--accent-cyan);
|
||||
}
|
||||
.ws-header.sooin {
|
||||
background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(124, 58, 237, 0.1));
|
||||
border-left: 3px solid var(--accent-purple);
|
||||
}
|
||||
.ws-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
.ws-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
.ws-count {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ws-empty {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 주문 확인 모달 */
|
||||
.order-modal {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user