feat: 실시간 POS 품목별 OTC 용법 라벨 인쇄 버튼 추가
POS 상세 패널:
- 품목 목록에 💊 인쇄 버튼 추가
- 프리셋 있으면 → 바로 인쇄
- 프리셋 없으면 → 새 창으로 등록 페이지 열기
API:
- /api/admin/pos-live/detail에 barcode 필드 추가
OTC 라벨 관리 페이지:
- URL 파라미터(barcode, name) 자동 처리
- POS에서 넘어올 때 자동으로 해당 약품 로드
This commit is contained in:
parent
c154537c87
commit
887aba3a03
@ -5070,14 +5070,15 @@ def api_admin_pos_live_detail(order_no):
|
||||
mssql_conn = mssql_engine.raw_connection()
|
||||
cursor = mssql_conn.cursor()
|
||||
|
||||
# 품목 상세 조회
|
||||
# 품목 상세 조회 (바코드 포함)
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
S.DrugCode AS drug_code,
|
||||
ISNULL(G.GoodsName, '(약품명 없음)') AS product_name,
|
||||
S.SL_NM_item AS quantity,
|
||||
S.SL_NM_cost_a AS unit_price,
|
||||
S.SL_TOTAL_PRICE AS total_price
|
||||
S.SL_TOTAL_PRICE AS total_price,
|
||||
G.Barcode AS barcode
|
||||
FROM SALE_SUB S
|
||||
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
||||
WHERE S.SL_NO_order = ?
|
||||
@ -5092,7 +5093,8 @@ def api_admin_pos_live_detail(order_no):
|
||||
'product_name': row[1],
|
||||
'quantity': int(row[2]) if row[2] else 0,
|
||||
'unit_price': float(row[3]) if row[3] else 0,
|
||||
'total_price': float(row[4]) if row[4] else 0
|
||||
'total_price': float(row[4]) if row[4] else 0,
|
||||
'barcode': row[5] or ''
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
|
||||
@ -401,6 +401,30 @@
|
||||
debounceTimer = setTimeout(previewLabel, 500);
|
||||
});
|
||||
});
|
||||
|
||||
// URL 파라미터로 바코드/이름 전달 시 자동 로드
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const urlBarcode = params.get('barcode');
|
||||
const urlName = params.get('name');
|
||||
if (urlBarcode) {
|
||||
currentBarcode = urlBarcode;
|
||||
currentDrugName = urlName || urlBarcode;
|
||||
document.getElementById('barcode').value = urlBarcode;
|
||||
document.getElementById('searchInput').value = urlName || urlBarcode;
|
||||
|
||||
// 기존 프리셋 확인
|
||||
fetch(`/api/admin/otc-labels/${urlBarcode}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.exists) {
|
||||
document.getElementById('displayName').value = data.label.display_name || '';
|
||||
document.getElementById('effect').value = data.label.effect || '';
|
||||
document.getElementById('dosageInstruction').value = data.label.dosage_instruction || '';
|
||||
document.getElementById('usageTip').value = data.label.usage_tip || '';
|
||||
}
|
||||
previewLabel();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 약품 검색 (MSSQL)
|
||||
|
||||
@ -400,6 +400,24 @@
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 600;
|
||||
}
|
||||
.item-label-btn {
|
||||
padding: 4px 8px;
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.item-label-btn:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 2px 8px rgba(245,158,11,0.4);
|
||||
}
|
||||
.item-label-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
/* ── 오버레이 ── */
|
||||
.overlay {
|
||||
@ -1139,6 +1157,7 @@
|
||||
<span class="detail-item-name">${item.product_name}</span>
|
||||
<span class="detail-item-qty">×${item.quantity}</span>
|
||||
<span class="detail-item-price">₩${Math.floor(item.total_price).toLocaleString()}</span>
|
||||
${item.barcode ? `<button class="item-label-btn" onclick="printOtcLabel('${item.barcode}', '${escapeHtml(item.product_name)}')">💊</button>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
document.getElementById('itemsList').innerHTML = itemsHtml;
|
||||
@ -1149,6 +1168,61 @@
|
||||
document.getElementById('itemsList').innerHTML = `<div class="empty-state">오류: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// OTC 용법 라벨 인쇄
|
||||
async function printOtcLabel(barcode, productName) {
|
||||
const btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '...';
|
||||
|
||||
try {
|
||||
// 프리셋 확인
|
||||
const checkRes = await fetch(`/api/admin/otc-labels/${barcode}`);
|
||||
const checkData = await checkRes.json();
|
||||
|
||||
if (checkData.exists) {
|
||||
// 프리셋 있음 → 바로 인쇄
|
||||
const preset = checkData.label;
|
||||
const printRes = await fetch('/api/admin/otc-labels/print', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
barcode: barcode,
|
||||
drug_name: preset.display_name || productName,
|
||||
effect: preset.effect || '',
|
||||
dosage_instruction: preset.dosage_instruction || '',
|
||||
usage_tip: preset.usage_tip || ''
|
||||
})
|
||||
});
|
||||
const printData = await printRes.json();
|
||||
|
||||
if (printData.success) {
|
||||
btn.textContent = '✓';
|
||||
btn.style.background = '#10b981';
|
||||
showToast(`용법 라벨 인쇄 완료!`, 'success');
|
||||
} else {
|
||||
btn.textContent = '💊';
|
||||
btn.disabled = false;
|
||||
showToast(printData.error || '인쇄 실패', 'error');
|
||||
}
|
||||
} else {
|
||||
// 프리셋 없음 → 새 창으로 등록 페이지 열기
|
||||
btn.textContent = '💊';
|
||||
btn.disabled = false;
|
||||
showToast('프리셋 미등록 → 등록 페이지로 이동', 'info');
|
||||
window.open(`/admin/otc-labels?barcode=${barcode}&name=${encodeURIComponent(productName)}`, '_blank', 'width=1200,height=800');
|
||||
}
|
||||
} catch (err) {
|
||||
btn.textContent = '💊';
|
||||
btn.disabled = false;
|
||||
showToast('오류: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 상세 패널에서 단일 건 처리
|
||||
|
||||
Loading…
Reference in New Issue
Block a user