From 887aba3a030edbe326b494bfd2fc9be4b85492ff Mon Sep 17 00:00:00 2001 From: thug0bin Date: Mon, 2 Mar 2026 17:14:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20POS=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=EB=B3=84=20OTC=20=EC=9A=A9=EB=B2=95=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9D=B8=EC=87=84=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POS 상세 패널: - 품목 목록에 💊 인쇄 버튼 추가 - 프리셋 있으면 → 바로 인쇄 - 프리셋 없으면 → 새 창으로 등록 페이지 열기 API: - /api/admin/pos-live/detail에 barcode 필드 추가 OTC 라벨 관리 페이지: - URL 파라미터(barcode, name) 자동 처리 - POS에서 넘어올 때 자동으로 해당 약품 로드 --- backend/app.py | 8 ++- backend/templates/admin_otc_labels.html | 24 ++++++++ backend/templates/admin_pos_live.html | 74 +++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/backend/app.py b/backend/app.py index 979b4d0..70e0841 100644 --- a/backend/app.py +++ b/backend/app.py @@ -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({ diff --git a/backend/templates/admin_otc_labels.html b/backend/templates/admin_otc_labels.html index cbef6bb..4951287 100644 --- a/backend/templates/admin_otc_labels.html +++ b/backend/templates/admin_otc_labels.html @@ -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) diff --git a/backend/templates/admin_pos_live.html b/backend/templates/admin_pos_live.html index 05ee683..235150a 100644 --- a/backend/templates/admin_pos_live.html +++ b/backend/templates/admin_pos_live.html @@ -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 @@ ${item.product_name} ×${item.quantity} ₩${Math.floor(item.total_price).toLocaleString()} + ${item.barcode ? `` : ''} `).join(''); document.getElementById('itemsList').innerHTML = itemsHtml; @@ -1149,6 +1168,61 @@ document.getElementById('itemsList').innerHTML = `
오류: ${err.message}
`; } } + + // 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]); + } // ═══════════════════════════════════════════════════════════════ // 상세 패널에서 단일 건 처리