diff --git a/backend/app.py b/backend/app.py
index 9a9edad..477d449 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -2796,26 +2796,43 @@ def _get_animal_drugs():
'barcode': barcode,
'apc': apc,
'stock': int(r.Stock) if r.Stock else 0,
+ 'wholesaler_stock': 0, # PostgreSQL에서 가져옴
'image_url': None # PostgreSQL에서 가져옴
})
- # PostgreSQL에서 이미지 URL 가져오기
+ # PostgreSQL에서 이미지 URL + 도매상 재고 가져오기
if apc_list:
try:
from sqlalchemy import create_engine
pg_engine = create_engine('postgresql://admin:trajet6640@192.168.0.87:5432/apdb_master')
with pg_engine.connect() as conn:
placeholders = ','.join([f"'{a}'" for a in apc_list])
+ # 이미지 URL 조회
img_result = conn.execute(text(f"""
SELECT apc, image_url1 FROM apc WHERE apc IN ({placeholders})
"""))
img_map = {row.apc: row.image_url1 for row in img_result}
+ # 도매상 재고 조회 (SUM)
+ stock_result = conn.execute(text(f"""
+ SELECT A.apc, COALESCE(SUM(I.quantity), 0) as wholesaler_stock
+ FROM apc A
+ LEFT JOIN inventory I ON I.apdb_id = A.idx
+ WHERE A.apc IN ({placeholders})
+ GROUP BY A.apc
+ """))
+ stock_map = {row.apc: int(row.wholesaler_stock) for row in stock_result}
+
for item in result:
- if item['apc'] and item['apc'] in img_map:
- item['image_url'] = img_map[item['apc']]
+ if item['apc']:
+ if item['apc'] in img_map:
+ item['image_url'] = img_map[item['apc']]
+ if item['apc'] in stock_map:
+ item['wholesaler_stock'] = stock_map[item['apc']]
+ else:
+ item['wholesaler_stock'] = 0
except Exception as e:
- logging.warning(f"PostgreSQL 이미지 URL 조회 실패: {e}")
+ logging.warning(f"PostgreSQL 이미지/재고 조회 실패: {e}")
return result
except Exception as e:
@@ -2973,7 +2990,8 @@ def api_animal_chat():
'price': drug['price'],
'code': drug['code'],
'image_url': drug.get('image_url'), # APC 이미지 URL
- 'stock': drug.get('stock', 0) # 재고
+ 'stock': drug.get('stock', 0), # 약국 재고
+ 'wholesaler_stock': drug.get('wholesaler_stock', 0) # 도매상 재고
})
return jsonify({
diff --git a/backend/templates/admin_products.html b/backend/templates/admin_products.html
index 0caef94..6a6cdc1 100644
--- a/backend/templates/admin_products.html
+++ b/backend/templates/admin_products.html
@@ -987,11 +987,15 @@
imgContainer.innerHTML = '
💊
';
}
- // 텍스트
+ // 텍스트 (약국/도매 재고)
const textDiv = document.createElement('div');
- const stockColor = (p.stock > 0) ? '#10b981' : '#ef4444';
- const stockText = (p.stock > 0) ? `재고 ${p.stock}` : '품절';
- textDiv.innerHTML = `${p.name}
${formatPrice(p.price)} ${stockText}
`;
+ const pharmacyStock = p.stock || 0;
+ const wholesalerStock = p.wholesaler_stock || 0;
+ const stockColor = (pharmacyStock > 0) ? '#10b981' : '#ef4444';
+ const pharmacyText = (pharmacyStock > 0) ? `약국 ${pharmacyStock}` : '품절';
+ const wholesalerText = (wholesalerStock > 0) ? `도매 ${wholesalerStock}` : '';
+ const stockDisplay = wholesalerText ? `${pharmacyText} / ${wholesalerText}` : pharmacyText;
+ textDiv.innerHTML = `${p.name}
${formatPrice(p.price)} ${stockDisplay}
`;
card.appendChild(imgContainer);
card.appendChild(textDiv);
diff --git a/docs/DATABASE_STRUCTURE.md b/docs/DATABASE_STRUCTURE.md
index 590d63b..a5db0a4 100644
--- a/docs/DATABASE_STRUCTURE.md
+++ b/docs/DATABASE_STRUCTURE.md
@@ -212,8 +212,98 @@ PostgreSQL에서 일부 제품은 APC 대신 **바코드**로 등록됨:
---
+## 재고 시스템 (2025-06-30)
+
+### 이중 재고 구조
+
+| 위치 | 테이블 | 용도 | 조회 방식 |
+|------|--------|------|-----------|
+| **MSSQL (PM_DRUG)** | `IM_total` | 약국 재고 | `IM_QT_sale_debit` |
+| **PostgreSQL** | `inventory` | 도매상 재고 | `SUM(quantity)` |
+
+### 약국 재고 (MSSQL)
+
+```sql
+-- IM_total 테이블
+SELECT DrugCode, IM_QT_sale_debit as stock
+FROM IM_total
+WHERE DrugCode = 'LB000003157';
+-- → 8 (현재 약국 보유 수량)
+```
+
+### 도매상 재고 (PostgreSQL)
+
+도매상 재고는 **입출고 이력**으로 관리됩니다.
+
+```sql
+-- inventory 테이블 (입출고 이력)
+-- quantity: +입고(INBOUND), -출고(OUTBOUND)
+
+SELECT A.apc, A.product_name, SUM(I.quantity) as wholesaler_stock
+FROM inventory I
+JOIN apc A ON I.apdb_id = A.idx
+WHERE A.for_pets = true
+GROUP BY A.apc, A.product_name
+HAVING SUM(I.quantity) > 0;
+
+-- 안텔민뽀삐: 38개
+-- 복합개시딘: 6개
+-- 세레니아16mg: 4개
+```
+
+### inventory 테이블 주요 컬럼
+
+| 컬럼 | 타입 | 설명 |
+|------|------|------|
+| apdb_id | integer | apc.idx FK |
+| quantity | integer | 수량 (+입고/-출고) |
+| transaction_type | varchar | INBOUND/OUTBOUND |
+| transaction_date | timestamp | 거래일시 |
+| wholesaler_price | numeric | 도매가 |
+| retail_price | numeric | 소매가 |
+| expiration_date | date | 유효기간 |
+
+### API 응답 예시
+
+```json
+{
+ "name": "안텔민뽀삐(5kg이하)",
+ "price": 5000,
+ "stock": 8, // 약국 재고
+ "wholesaler_stock": 38 // 도매상 재고
+}
+```
+
+### 프론트엔드 표시
+
+```
+┌────────────────────────────────┐
+│ 💊 안텔민뽀삐(5kg이하) │
+│ ₩5,000 약국 8 / 도매 38 │
+└────────────────────────────────┘
+```
+
+- **약국 재고 있음**: 초록색 `약국 8`
+- **약국 품절**: 빨간색 `품절`
+- **도매상 재고**: 파란색 `도매 38` (발주 가능)
+
+---
+
+## 향후 계획: 연관 제품 추천
+
+약국에 없지만 도매상에 있는 제품 추천 로직:
+
+1. **카테고리 기반**: 같은 efficacy_effect (심장사상충, 외부기생충 등)
+2. **신제품**: PostgreSQL `created_at` 최신순
+3. **인기 제품**: 도매상 출고량 기준 (`transaction_type = 'OUTBOUND'` 집계)
+
+→ 클릭 시 발주 연결 (미구현)
+
+---
+
## 관련 파일
- `backend/app.py`: `_get_animal_drugs()`, `_get_animal_drug_rag()`
- `backend/scripts/insert_apc_*.py`: APC INSERT 스크립트
+- `backend/scripts/check_pgsql_stock_sum.py`: 도매상 재고 확인
- `docs/APC_MAPPING_PLAN.md`: APC 매핑 기획