diff --git a/backend/app.py b/backend/app.py index 4a0b182..fe262f0 100644 --- a/backend/app.py +++ b/backend/app.py @@ -3067,8 +3067,10 @@ def api_products(): ELSE '' END as supplier, CASE WHEN SET_CHK.is_set = 1 THEN 1 ELSE 0 END as is_set, - G.POS_BOON as pos_boon + G.POS_BOON as pos_boon, + ISNULL(IT.IM_QT_sale_debit, 0) as stock FROM CD_GOODS G + LEFT JOIN IM_total IT ON G.DrugCode = IT.DrugCode OUTER APPLY ( SELECT TOP 1 CD_CD_BARCODE FROM CD_ITEM_UNIT_MEMBER @@ -3108,7 +3110,8 @@ def api_products(): 'cost_price': float(row.cost_price) if row.cost_price else 0, 'supplier': row.supplier or '', 'is_set': bool(row.is_set), - 'is_animal_drug': is_animal + 'is_animal_drug': is_animal, + 'stock': int(row.stock) if row.stock else 0 }) return jsonify({ diff --git a/backend/scripts/debug_gesidin_match.py b/backend/scripts/debug_gesidin_match.py new file mode 100644 index 0000000..9fabc19 --- /dev/null +++ b/backend/scripts/debug_gesidin_match.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +import sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') + +# 테스트 AI 응답 +ai_response = "개시딘은 피부염 치료에 사용하는 겔 형태의 외용약입니다." + +drug_name = "(판)복합개시딘" + +# 현재 매칭 로직 +base_name = drug_name.split('(')[0].split('/')[0].strip() +print(f'제품명: {drug_name}') +print(f'괄호 앞: "{base_name}"') + +# suffix 제거 +for suffix in ['정', '액', 'L', 'M', 'S', 'XL', 'XS', 'SS', 'mini']: + if base_name.endswith(suffix): + base_name = base_name[:-len(suffix)] +base_name = base_name.strip() +print(f'suffix 제거 후: "{base_name}"') + +# 매칭 테스트 +ai_lower = ai_response.lower() +ai_nospace = ai_lower.replace(' ', '') +base_lower = base_name.lower() +base_nospace = base_lower.replace(' ', '') + +print(f'\n매칭 테스트:') +print(f' "{base_lower}" in ai_response? {base_lower in ai_lower}') +print(f' "{base_nospace}" in ai_nospace? {base_nospace in ai_nospace}') + +# 문제: (판)이 먼저 잘려서 빈 문자열이 됨! +print(f'\n문제: split("(")[0] = "{drug_name.split("(")[0]}"') +print('→ "(판)"에서 "("로 시작하니까 빈 문자열!') diff --git a/backend/scripts/fix_gesidin_boon.py b/backend/scripts/fix_gesidin_boon.py new file mode 100644 index 0000000..3504e89 --- /dev/null +++ b/backend/scripts/fix_gesidin_boon.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +sys.path.insert(0, 'c:\\Users\\청춘약국\\source\\pharmacy-pos-qr-system\\backend') + +from db.dbsetup import get_db_session +from sqlalchemy import text + +session = get_db_session('PM_DRUG') + +print('업데이트 전:') +r = session.execute(text("SELECT GoodsName, POS_BOON FROM CD_GOODS WHERE DrugCode = 'LB000003140'")).fetchone() +print(f' {r.GoodsName}: POS_BOON = {r.POS_BOON}') + +session.execute(text("UPDATE CD_GOODS SET POS_BOON = '010103' WHERE DrugCode = 'LB000003140'")) +session.commit() + +print('\n업데이트 후:') +r2 = session.execute(text("SELECT GoodsName, POS_BOON FROM CD_GOODS WHERE DrugCode = 'LB000003140'")).fetchone() +print(f' {r2.GoodsName}: POS_BOON = {r2.POS_BOON}') +print(' ✅ 완료!') + +session.close() diff --git a/backend/scripts/list_petfarm.py b/backend/scripts/list_petfarm.py new file mode 100644 index 0000000..1ed86c1 --- /dev/null +++ b/backend/scripts/list_petfarm.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +sys.path.insert(0, 'c:\\Users\\청춘약국\\source\\pharmacy-pos-qr-system\\backend') + +from db.dbsetup import get_db_session +from sqlalchemy import text + +session = get_db_session('PM_DRUG') + +print('=== 펫팜 공급 동물약 ===\n') +result = session.execute(text(""" + SELECT + G.DrugCode, + G.GoodsName, + G.POS_BOON, + S.SplName, + ( + SELECT TOP 1 U.CD_CD_BARCODE + FROM CD_ITEM_UNIT_MEMBER U + WHERE U.DRUGCODE = G.DrugCode + AND U.CD_CD_BARCODE LIKE '023%' + ) AS APC_CODE + FROM CD_GOODS G + LEFT JOIN CD_SALEGOODS S ON G.DrugCode = S.DrugCode + WHERE S.SplName LIKE N'%펫팜%' + ORDER BY G.GoodsName +""")) + +for row in result: + apc_status = f'✅ {row.APC_CODE}' if row.APC_CODE else '❌ 없음' + boon_status = '🐾' if row.POS_BOON == '010103' else ' ' + print(f'{boon_status} {row.GoodsName}') + print(f' APC: {apc_status}') + +session.close() diff --git a/backend/scripts/test_gestage_api.py b/backend/scripts/test_gestage_api.py new file mode 100644 index 0000000..a8dede4 --- /dev/null +++ b/backend/scripts/test_gestage_api.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +sys.path.insert(0, 'c:\\Users\\청춘약국\\source\\pharmacy-pos-qr-system\\backend') + +from app import _get_animal_drugs + +drugs = _get_animal_drugs() +gestage = [d for d in drugs if '제스타제' in d['name']] + +print('=== 제스타제 API 결과 ===\n') +for d in gestage: + print(f"name: {d['name']}") + print(f"barcode: {d['barcode']}") + print(f"apc: {d['apc']}") + print(f"image_url: {d['image_url']}") diff --git a/backend/templates/admin_products.html b/backend/templates/admin_products.html index 819efb1..2b401ec 100644 --- a/backend/templates/admin_products.html +++ b/backend/templates/admin_products.html @@ -3,7 +3,7 @@ - 제품 검색 - 청춘약국 + ?�품 검??- �?��?�국 @@ -16,7 +16,7 @@ color: #1e293b; } - /* ── 헤더 ── */ + /* ?�?� ?�더 ?�?� */ .header { background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 50%, #a78bfa 100%); padding: 28px 32px 24px; @@ -46,14 +46,14 @@ opacity: 0.85; } - /* ── 컨텐츠 ── */ + /* ?�?� 컨텐�??�?� */ .content { max-width: 1100px; margin: 0 auto; padding: 24px 20px 60px; } - /* ── 플로팅 챗봇 ── */ + /* ?�?� ?�로??챗봇 ?�?� */ .chatbot-panel { position: fixed; right: 24px; @@ -224,7 +224,7 @@ 30% { transform: translateY(-6px); opacity: 1; } } - /* ── 챗봇 토글 버튼 (항상 표시) ── */ + /* ?�?� 챗봇 ?��? 버튼 (??�� ?�시) ?�?� */ .chatbot-toggle { position: fixed; bottom: 24px; @@ -253,7 +253,7 @@ box-shadow: 0 4px 20px rgba(239, 68, 68, 0.4); } - /* 모바일 */ + /* 모바??*/ @media (max-width: 640px) { .chatbot-panel { right: 0; @@ -266,7 +266,7 @@ .chatbot-toggle { bottom: 16px; right: 16px; } } - /* ── 검색 영역 ── */ + /* ?�?� 검???�역 ?�?� */ .search-section { background: #fff; border-radius: 14px; @@ -320,7 +320,7 @@ margin-right: 8px; } - /* ── 결과 카운트 ── */ + /* ?�?� 결과 카운???�?� */ .result-count { margin-bottom: 16px; font-size: 14px; @@ -331,7 +331,7 @@ font-weight: 700; } - /* ── 테이블 ── */ + /* ?�?� ?�이�??�?� */ .table-wrap { background: #fff; border-radius: 14px; @@ -362,7 +362,7 @@ tbody tr:hover { background: #faf5ff; } tbody tr:last-child td { border-bottom: none; } - /* ── 상품 정보 ── */ + /* ?�?� ?�품 ?�보 ?�?� */ .product-name { font-weight: 600; color: #1e293b; @@ -377,7 +377,7 @@ font-weight: 500; } - /* ── 코드/바코드 ── */ + /* ?�?� 코드/바코???�?� */ .code { font-family: 'JetBrains Mono', monospace; font-size: 12px; @@ -398,14 +398,25 @@ color: #94a3b8; } - /* ── 가격 ── */ + /* 가격 */ .price { font-weight: 600; color: #1e293b; white-space: nowrap; } + /* 재고 */ + .stock { + font-weight: 600; + white-space: nowrap; + } + .stock.in-stock { + color: #10b981; + } + .stock.out-stock { + color: #ef4444; + } - /* ── QR 버튼 ── */ + /* QR 버튼 */ .btn-qr { background: #8b5cf6; color: #fff; @@ -421,7 +432,7 @@ .btn-qr:hover { background: #7c3aed; } .btn-qr:active { transform: scale(0.95); } - /* ── 빈 상태 ── */ + /* ?�?� �??�태 ?�?� */ .empty-state { text-align: center; padding: 60px 20px; @@ -435,7 +446,7 @@ font-size: 15px; } - /* ── 모달 ── */ + /* ?�?� 모달 ?�?� */ .modal-overlay { display: none; position: fixed; @@ -471,7 +482,7 @@ border-radius: 8px; } - /* ── 수량 선택기 ── */ + /* ?�?� ?�량 ?�택�??�?� */ .qty-selector { display: flex; align-items: center; @@ -539,7 +550,7 @@ .modal-btn.confirm { background: #8b5cf6; color: #fff; } .modal-btn.confirm:hover { background: #7c3aed; } - /* ── 반응형 ── */ + /* ?�?� 반응???�?� */ @media (max-width: 768px) { .search-box { flex-direction: column; } .table-wrap { overflow-x: auto; } @@ -550,33 +561,33 @@
- ← 관리자 홈 + ??관리자 ??/a>
- 판매 조회 - 판매 내역 + ?�매 조회 + ?�매 ?�역
-

🔍 제품 검색

-

전체 제품 검색 · QR 라벨 인쇄 · 🐾 동물약 AI 상담

+

?�� ?�품 검??/h1> +

?�체 ?�품 검??· QR ?�벨 ?�쇄 · ?�� ?�물??AI ?�담

- +
- 예시 타이레놀, 벤포파워, 8806418067510, LB000001423 + ?�시 ?�?�레?�, 벤포?�워, 8806418067510, LB000001423
@@ -584,25 +595,25 @@ + 검??결과: 0�?
- - - - + + + - @@ -610,51 +621,51 @@ - +
-

🐾 동물약 AI 상담

-

심장사상충, 외부기생충, 구충제 등 무엇이든 물어보세요

+

?�� ?�물??AI ?�담

+

?�장?�상�? ?��?기생�? 구충????무엇?�든 물어보세??/p>

- - - - + + +
- 안녕하세요! 🐾 동물약 상담 AI입니다.

- 반려동물의 심장사상충 예방, 벼룩/진드기 예방, 구충제 등에 대해 무엇이든 물어보세요! + ?�녕?�세?? ?�� ?�물???�담 AI?�니??

+ 반려?�물???�장?�상�??�방, 벼룩/진드�??�방, 구충??/strong> ?�에 ?�??무엇?�든 물어보세??
- - +
- - + + - + @@ -668,7 +679,7 @@ function formatPrice(num) { if (!num) return '-'; - return new Intl.NumberFormat('ko-KR').format(num) + '원'; + return new Intl.NumberFormat('ko-KR').format(num) + '??; } function escapeHtml(str) { @@ -679,16 +690,16 @@ function searchProducts() { const search = document.getElementById('searchInput').value.trim(); if (!search) { - alert('검색어를 입력하세요'); + alert('검?�어�??�력?�세??); return; } if (search.length < 2) { - alert('2글자 이상 입력하세요'); + alert('2글???�상 ?�력?�세??); return; } const tbody = document.getElementById('productsTableBody'); - tbody.innerHTML = ''; + tbody.innerHTML = ''; const animalOnly = document.getElementById('animalOnly').checked; fetch(`/api/products?search=${encodeURIComponent(search)}${animalOnly ? '&animal_only=1' : ''}`) @@ -700,11 +711,11 @@ document.getElementById('resultNum').textContent = productsData.length; renderTable(); } else { - tbody.innerHTML = ``; + tbody.innerHTML = ``; } }) .catch(err => { - tbody.innerHTML = ''; + tbody.innerHTML = ''; }); } @@ -712,7 +723,7 @@ const tbody = document.getElementById('productsTableBody'); if (productsData.length === 0) { - tbody.innerHTML = ''; + tbody.innerHTML = ''; return; } @@ -721,23 +732,24 @@ + : `?�음`} + `).join(''); } - // ── QR 인쇄 관련 ── + // ?�?� QR ?�쇄 관???�?� function adjustQty(delta) { printQty = Math.max(MIN_QTY, Math.min(MAX_QTY, printQty + delta)); updateQtyUI(); @@ -747,7 +759,7 @@ document.getElementById('qtyValue').textContent = printQty; document.getElementById('qtyMinus').disabled = printQty <= MIN_QTY; document.getElementById('qtyPlus').disabled = printQty >= MAX_QTY; - document.getElementById('printBtn').textContent = printQty > 1 ? `${printQty}장 인쇄` : '인쇄'; + document.getElementById('printBtn').textContent = printQty > 1 ? `${printQty}???�쇄` : '?�쇄'; } function printQR(idx) { @@ -758,12 +770,12 @@ const preview = document.getElementById('qrPreview'); const info = document.getElementById('qrInfo'); - preview.innerHTML = '

미리보기 로딩 중...

'; + preview.innerHTML = '

미리보기 로딩 �?..

'; info.innerHTML = ` ${escapeHtml(selectedItem.product_name)}
- 바코드: ${selectedItem.barcode || selectedItem.drug_code || 'N/A'}
- 가격: ${formatPrice(selectedItem.sale_price)} + 바코?? ${selectedItem.barcode || selectedItem.drug_code || 'N/A'}
+ 가�? ${formatPrice(selectedItem.sale_price)}
`; updateQtyUI(); @@ -784,11 +796,11 @@ if (data.success && data.image) { preview.innerHTML = `QR 미리보기`; } else { - preview.innerHTML = '

미리보기 실패

'; + preview.innerHTML = '

미리보기 ?�패

'; } }) .catch(() => { - preview.innerHTML = '

미리보기 오류

'; + preview.innerHTML = '

미리보기 ?�류

'; }); } @@ -809,7 +821,7 @@ let errorMsg = ''; for (let i = 0; i < totalQty; i++) { - btn.textContent = `인쇄 중... (${i + 1}/${totalQty})`; + btn.textContent = `?�쇄 �?.. (${i + 1}/${totalQty})`; try { const res = await fetch('/api/qr-print', { @@ -827,7 +839,7 @@ if (data.success) { successCount++; } else { - errorMsg = data.error || '알 수 없는 오류'; + errorMsg = data.error || '?????�는 ?�류'; break; } @@ -844,21 +856,20 @@ updateQtyUI(); if (successCount === totalQty) { - alert(`✅ QR 라벨 ${totalQty}장 인쇄 완료!`); + alert(`??QR ?�벨 ${totalQty}???�쇄 ?�료!`); closeQRModal(); } else if (successCount > 0) { - alert(`⚠️ ${successCount}/${totalQty}장 인쇄 완료\n오류: ${errorMsg}`); + alert(`?�️ ${successCount}/${totalQty}???�쇄 ?�료\n?�류: ${errorMsg}`); } else { - alert(`❌ 인쇄 실패: ${errorMsg}`); + alert(`???�쇄 ?�패: ${errorMsg}`); } } - // 페이지 로드 시 검색창 포커스 - document.getElementById('searchInput').focus(); + // ?�이지 로드 ??검?�창 ?�커?? document.getElementById('searchInput').focus(); - // ══════════════════════════════════════════════════════════════════ - // 동물약 챗봇 - // ══════════════════════════════════════════════════════════════════ + // ?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═ + // ?�물??챗봇 + // ?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═?�═ let chatHistory = []; let isChatLoading = false; @@ -867,7 +878,7 @@ const btn = document.getElementById('chatbotToggle'); const isOpen = panel.classList.toggle('open'); btn.classList.toggle('active', isOpen); - btn.innerHTML = isOpen ? '✕' : '🐾'; + btn.innerHTML = isOpen ? '?? : '?��'; if (isOpen) { document.getElementById('chatInput').focus(); } @@ -884,14 +895,14 @@ if (!message || isChatLoading) return; - // 사용자 메시지 표시 + // ?�용??메시지 ?�시 addChatMessage('user', message); input.value = ''; - // 히스토리에 추가 + // ?�스?�리??추�? chatHistory.push({ role: 'user', content: message }); - // 로딩 표시 + // 로딩 ?�시 isChatLoading = true; document.getElementById('chatSendBtn').disabled = true; showTypingIndicator(); @@ -908,22 +919,22 @@ hideTypingIndicator(); if (data.success) { - // AI 응답 표시 + // AI ?�답 ?�시 addChatMessage('assistant', data.message, data.products); - // 히스토리에 추가 + // ?�스?�리??추�? chatHistory.push({ role: 'assistant', content: data.message }); - // 히스토리 길이 제한 (최근 20개) + // ?�스?�리 길이 ?�한 (최근 20�? if (chatHistory.length > 20) { chatHistory = chatHistory.slice(-20); } } else { - addChatMessage('system', '⚠️ ' + (data.message || '오류가 발생했습니다')); + addChatMessage('system', '?�️ ' + (data.message || '?�류가 발생?�습?�다')); } } catch (error) { hideTypingIndicator(); - addChatMessage('system', '⚠️ 네트워크 오류가 발생했습니다'); + addChatMessage('system', '?�️ ?�트?�크 ?�류가 발생?�습?�다'); } isChatLoading = false; @@ -935,19 +946,19 @@ const msgDiv = document.createElement('div'); msgDiv.className = `chat-message ${role}`; - // 줄바꿈 처리 + // 줄바�?처리 let htmlContent = escapeHtml(content).replace(/\n/g, '
'); - // 마크다운 굵게 처리 + // 마크?�운 굵게 처리 htmlContent = htmlContent.replace(/\*\*(.+?)\*\*/g, '$1'); msgDiv.innerHTML = htmlContent; - // 언급된 제품 표시 (이미지 포함) + // ?�급???�품 ?�시 (?��?지 ?�함) if (products && products.length > 0) { const productsDiv = document.createElement('div'); productsDiv.className = 'products-mentioned'; - productsDiv.innerHTML = '📦 관련 제품:'; + productsDiv.innerHTML = '?�� 관???�품:'; const productsGrid = document.createElement('div'); productsGrid.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;'; @@ -958,7 +969,7 @@ card.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px;background:#f8fafc;border-radius:8px;cursor:pointer;border:1px solid #e2e8f0;'; card.onclick = () => searchProductFromChat(p.name); - // 이미지 컨테이너 + // ?��?지 컨테?�너 const imgContainer = document.createElement('div'); imgContainer.style.cssText = 'width:40px;height:40px;flex-shrink:0;'; @@ -968,17 +979,14 @@ img.src = p.image_url; img.alt = p.name; img.onerror = function() { - // 이미지 로드 실패 시 아이콘으로 대체 - imgContainer.innerHTML = '
💊
'; + // ?��?지 로드 ?�패 ???�이콘으�??��? imgContainer.innerHTML = '
?��
'; }; imgContainer.appendChild(img); } else { - // 이미지 없으면 아이콘 - imgContainer.innerHTML = '
💊
'; + // ?��?지 ?�으�??�이�? imgContainer.innerHTML = '
?��
'; } - // 텍스트 - const textDiv = document.createElement('div'); + // ?�스?? const textDiv = document.createElement('div'); textDiv.innerHTML = `
${p.name}
${formatPrice(p.price)}
`; card.appendChild(imgContainer); @@ -1010,12 +1018,11 @@ } function searchProductFromChat(productName) { - // 챗봇에서 제품 클릭 시 검색창에 입력하고 검색 - document.getElementById('searchInput').value = productName; + // 챗봇?�서 ?�품 ?�릭 ??검?�창???�력?�고 검?? document.getElementById('searchInput').value = productName; document.getElementById('animalOnly').checked = true; searchProducts(); - // 모바일에서 챗봇 닫기 + // 모바?�에??챗봇 ?�기 if (window.innerWidth <= 1100) { document.getElementById('chatbotPanel').classList.remove('open'); }
상품명상품코드바코드판매가?�품�?/th> + ?�품코드바코??/th> + ?�고?�매가 QR
-
🔍
-

상품명, 바코드, 상품코드로 검색하세요

+
+
?��
+

?�품�? 바코?? ?�품코드�?검?�하?�요

검색 중...

검??�?..

오류: ${data.error}

?�류: ${data.error}

검색 실패

검???�패

📭

검색 결과가 없습니다

?��

검??결과가 ?�습?�다

${escapeHtml(item.product_name)} - ${item.is_animal_drug ? '🐾 동물약' : ''} + ${item.is_animal_drug ? '?�� ?�물??/span>' : ''}
${escapeHtml(item.supplier) || ''}
${item.drug_code} ${item.barcode ? `${item.barcode}` - : `없음`}${item.stock || 0}�?/td> ${formatPrice(item.sale_price)} - +