feat: QR 토큰 품목 상세 전송 지원 (items 파라미터)
This commit is contained in:
564
docs/LABEL_PRINTING_GUIDE.md
Normal file
564
docs/LABEL_PRINTING_GUIDE.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# 라벨 인쇄 시스템 가이드
|
||||
|
||||
> pharmacy-pos-qr-system의 라벨 인쇄/미리보기 기능 문서
|
||||
>
|
||||
> 작성일: 2026-03-18
|
||||
|
||||
---
|
||||
|
||||
## 📁 파일 구조
|
||||
|
||||
```
|
||||
backend/
|
||||
├── pmr_api.py # 처방전(PMR) 라벨 인쇄 API
|
||||
├── qr_printer.py # 약품 QR 라벨 (바코드/가격)
|
||||
├── utils/
|
||||
│ ├── otc_label_printer.py # OTC 용법 라벨 (가로형 와이드)
|
||||
│ └── qr_label_printer.py # QR 영수증 라벨 (마일리지용)
|
||||
└── samples/
|
||||
└── print_label.py # 처방전 라벨 핵심 함수 (참조용)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖨️ 프린터 설정
|
||||
|
||||
| 용도 | 모델 | IP | 포트 | 용지 |
|
||||
|------|------|-----|------|------|
|
||||
| QR 라벨 (121) | Brother QL-710W | 192.168.0.121 | 9100 | 29mm 연속 |
|
||||
| OTC 라벨 (168) | Brother QL-810W | 192.168.0.168 | 9100 | 29mm 연속 |
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ 처방전 라벨 (PMR)
|
||||
|
||||
### 파일 위치
|
||||
- **API**: `backend/pmr_api.py`
|
||||
- **엔드포인트**: `/pmr/api/label/preview`, `/pmr/api/label/print`
|
||||
|
||||
### 미리보기 API
|
||||
|
||||
```
|
||||
POST /pmr/api/label/preview
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"patient_name": "홍길동",
|
||||
"med_name": "아모잘탄정5/50mg",
|
||||
"add_info": "고혈압치료제",
|
||||
"dosage": 1.0,
|
||||
"frequency": 2,
|
||||
"duration": 30,
|
||||
"unit": "정",
|
||||
"sung_code": "123456TB"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"image": "data:image/png;base64,iVBORw0KGgo...",
|
||||
"conversion_factor": null,
|
||||
"storage_conditions": "실온보관"
|
||||
}
|
||||
```
|
||||
|
||||
### 인쇄 API
|
||||
|
||||
```
|
||||
POST /pmr/api/label/print
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"patient_name": "홍길동",
|
||||
"med_name": "아모잘탄정5/50mg",
|
||||
"add_info": "고혈압치료제",
|
||||
"dosage": 1.0,
|
||||
"frequency": 2,
|
||||
"duration": 30,
|
||||
"unit": "정",
|
||||
"sung_code": "123456TB",
|
||||
"printer": "168",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 필수 | 설명 |
|
||||
|----------|------|------|------|
|
||||
| patient_name | string | ✅ | 환자명 |
|
||||
| med_name | string | ✅ | 약품명 |
|
||||
| add_info | string | ❌ | 효능/분류 (PRINT_TYPE) |
|
||||
| dosage | float | ✅ | 1회 복용량 |
|
||||
| frequency | int | ✅ | 1일 복용 횟수 (1,2,3...) |
|
||||
| duration | int | ✅ | 복용 일수 |
|
||||
| unit | string | ✅ | 단위 (정, 캡슐, mL, 포, g) |
|
||||
| sung_code | string | ❌ | 성분코드 (환산계수 조회용) |
|
||||
| printer | string | ❌ | "121" 또는 "168" (기본: 168) |
|
||||
| orientation | string | ❌ | "portrait" 또는 "landscape" (기본: portrait) |
|
||||
|
||||
### 핵심 함수 (`pmr_api.py`)
|
||||
|
||||
```python
|
||||
def create_label_image(patient_name, med_name, add_info='', dosage=0,
|
||||
frequency=0, duration=0, unit='정',
|
||||
conversion_factor=None, storage_conditions='실온보관'):
|
||||
"""
|
||||
라벨 이미지 생성 (29mm 용지 기준, 306x380px)
|
||||
|
||||
Returns:
|
||||
PIL.Image: RGB 이미지
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def normalize_medication_name(med_name):
|
||||
"""
|
||||
약품명 정제
|
||||
- 밀리그램 → mg
|
||||
- 마이크로그램 → μg
|
||||
- 밀리리터 → mL
|
||||
- 언더스코어 뒤 내용 제거
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def get_drug_unit(goods_name, sung_code):
|
||||
"""
|
||||
SUNG_CODE 마지막 2자리로 단위 판별
|
||||
- TB, TA, TC... → "정"
|
||||
- CA, CH, CS... → "캡슐"
|
||||
- SS, SY, LQ... → "mL"
|
||||
- GA, GB, PD... → "포"
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ OTC 용법 라벨 (가로형 와이드)
|
||||
|
||||
### 파일 위치
|
||||
- **모듈**: `backend/utils/otc_label_printer.py`
|
||||
- **API**: `backend/app.py`
|
||||
|
||||
### 미리보기 API
|
||||
|
||||
```
|
||||
POST /api/admin/otc-labels/preview
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"drug_name": "타이레놀정500mg",
|
||||
"effect": "해열·진통",
|
||||
"dosage_instruction": "1일 3회, 1회 1~2정 [식후 30분]",
|
||||
"usage_tip": "공복 복용 시 위장장애 주의"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"preview_url": "data:image/png;base64,iVBORw0KGgo..."
|
||||
}
|
||||
```
|
||||
|
||||
### 인쇄 API
|
||||
|
||||
```
|
||||
POST /api/admin/otc-labels/print
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"drug_name": "타이레놀정500mg",
|
||||
"effect": "해열·진통",
|
||||
"dosage_instruction": "1일 3회, 1회 1~2정 [식후 30분]",
|
||||
"usage_tip": "공복 복용 시 위장장애 주의",
|
||||
"barcode": "8806436044814"
|
||||
}
|
||||
```
|
||||
|
||||
### 바코드로 인쇄 (간편)
|
||||
|
||||
```
|
||||
GET /api/otc-label-print/<barcode>
|
||||
```
|
||||
|
||||
예: `GET /api/otc-label-print/8806436044814`
|
||||
|
||||
> DB의 `otc_label_presets` 테이블에서 미리 저장된 라벨 정보 사용
|
||||
|
||||
### 핵심 함수 (`utils/otc_label_printer.py`)
|
||||
|
||||
```python
|
||||
def create_otc_label_image(drug_name, effect="", dosage_instruction="", usage_tip=""):
|
||||
"""
|
||||
OTC 용법 라벨 이미지 생성 (800 x 306px 가로형)
|
||||
|
||||
레이아웃:
|
||||
- 효능: 중앙 상단 72pt (매우 크게!)
|
||||
- 약품명: 오른쪽 중간 36pt
|
||||
- 용법: 왼쪽 하단 40pt (체크박스 포함)
|
||||
- 약국명: 오른쪽 하단 테두리 박스
|
||||
|
||||
Returns:
|
||||
PIL.Image: 1-bit 이미지 (흑백)
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def generate_preview_image(drug_name, effect="", dosage_instruction="", usage_tip=""):
|
||||
"""
|
||||
미리보기용 Base64 PNG 이미지 반환
|
||||
|
||||
Returns:
|
||||
str: "data:image/png;base64,..." 형태
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def print_otc_label(drug_name, effect="", dosage_instruction="", usage_tip=""):
|
||||
"""
|
||||
Brother QL-810W (192.168.0.168)로 인쇄
|
||||
- 이미지 90도 회전 후 전송
|
||||
|
||||
Returns:
|
||||
bool: 성공 여부
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 약품 QR 라벨 (바코드/가격)
|
||||
|
||||
### 파일 위치
|
||||
- **모듈**: `backend/qr_printer.py`
|
||||
- **API**: `backend/app.py`
|
||||
|
||||
### 미리보기 API
|
||||
|
||||
```
|
||||
POST /api/qr-preview
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"drug_name": "벤포파워Z",
|
||||
"barcode": "8806418067510",
|
||||
"sale_price": 3000,
|
||||
"drug_code": "A12345678"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"preview_url": "data:image/png;base64,iVBORw0KGgo..."
|
||||
}
|
||||
```
|
||||
|
||||
### 인쇄 API
|
||||
|
||||
```
|
||||
POST /api/qr-print
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"drug_name": "벤포파워Z",
|
||||
"barcode": "8806418067510",
|
||||
"sale_price": 3000,
|
||||
"drug_code": "A12345678"
|
||||
}
|
||||
```
|
||||
|
||||
### 핵심 함수 (`qr_printer.py`)
|
||||
|
||||
```python
|
||||
def create_drug_qr_label(drug_name, barcode, sale_price, drug_code=None,
|
||||
pharmacy_name='청춘약국'):
|
||||
"""
|
||||
약품 QR 라벨 이미지 생성 (306 x 380px)
|
||||
|
||||
구조:
|
||||
┌─────────────────┐
|
||||
│ [QR CODE] │ ← 바코드 기반 QR (130x130px)
|
||||
├─────────────────┤
|
||||
│ 약품명 │ ← 중앙 정렬 (최대 2줄)
|
||||
├─────────────────┤
|
||||
│ ₩12,000 │ ← 판매가격
|
||||
├─────────────────┤
|
||||
│ 청 춘 약 국 │ ← 테두리 박스
|
||||
└─────────────────┘
|
||||
|
||||
Returns:
|
||||
PIL.Image: 1-bit 이미지
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def print_drug_qr_label(drug_name, barcode, sale_price, drug_code=None,
|
||||
pharmacy_name='청춘약국'):
|
||||
"""
|
||||
Brother QL-710W (192.168.0.121)로 인쇄
|
||||
|
||||
Returns:
|
||||
dict: {"success": True/False, "message": "...", "error": "..."}
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def preview_qr_label(drug_name, barcode, sale_price, drug_code=None,
|
||||
pharmacy_name='청춘약국'):
|
||||
"""
|
||||
미리보기용 Base64 PNG 반환
|
||||
|
||||
Returns:
|
||||
str: "data:image/png;base64,..."
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ QR 영수증 라벨 (마일리지용)
|
||||
|
||||
### 파일 위치
|
||||
- **모듈**: `backend/utils/qr_label_printer.py`
|
||||
- **API**: `backend/app.py`
|
||||
|
||||
### 인쇄 API
|
||||
|
||||
```
|
||||
POST /api/admin/qr/print
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"transaction_id": "20251024000042",
|
||||
"total_amount": 50000,
|
||||
"claimable_points": 1500,
|
||||
"transaction_time": "2025-10-24T14:30:00",
|
||||
"token_raw": "abc123",
|
||||
"printer": "brother"
|
||||
}
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|----------|------|------|
|
||||
| transaction_id | string | 거래 번호 |
|
||||
| total_amount | float | 결제 금액 |
|
||||
| claimable_points | int | 적립 예정 포인트 |
|
||||
| transaction_time | string | 거래 시간 (ISO 8601) |
|
||||
| token_raw | string | QR URL 생성용 토큰 |
|
||||
| printer | string | "brother" 또는 "pos" |
|
||||
|
||||
### 핵심 함수 (`utils/qr_label_printer.py`)
|
||||
|
||||
```python
|
||||
def create_qr_receipt_label(qr_url, transaction_id, total_amount,
|
||||
claimable_points, transaction_time):
|
||||
"""
|
||||
QR 영수증 라벨 이미지 생성 (800 x 306px 가로형)
|
||||
|
||||
레이아웃:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [청춘약국] [QR CODE] │
|
||||
│ 2025-10-24 14:30 200x200px │
|
||||
│ 거래: 20251024000042 │
|
||||
│ │
|
||||
│ 결제금액: 50,000원 │
|
||||
│ 적립예정: 1,500P │
|
||||
│ │
|
||||
│ QR 촬영하고 포인트 받으세요! │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Returns:
|
||||
PIL.Image: 1-bit 이미지
|
||||
"""
|
||||
```
|
||||
|
||||
```python
|
||||
def print_qr_label(qr_url, transaction_id, total_amount, claimable_points,
|
||||
transaction_time, preview_mode=False):
|
||||
"""
|
||||
QR 라벨 출력 또는 미리보기
|
||||
|
||||
Args:
|
||||
preview_mode: True = 미리보기 (파일 저장), False = 인쇄
|
||||
|
||||
Returns:
|
||||
preview_mode=True: (성공 여부, 이미지 파일 경로)
|
||||
preview_mode=False: 성공 여부 (bool)
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 공통 유틸리티
|
||||
|
||||
### 지그재그 테두리 (절취선)
|
||||
|
||||
```python
|
||||
def draw_scissor_border(draw, width, height, edge_size=10, steps=20):
|
||||
"""
|
||||
라벨 테두리에 톱니 모양 절취선 그리기
|
||||
|
||||
Args:
|
||||
draw: ImageDraw 객체
|
||||
width: 라벨 너비
|
||||
height: 라벨 높이
|
||||
edge_size: 톱니 크기 (px)
|
||||
steps: 톱니 개수
|
||||
"""
|
||||
```
|
||||
|
||||
### 중앙 정렬 텍스트
|
||||
|
||||
```python
|
||||
def draw_centered_text(draw, text, y, font, max_width=None):
|
||||
"""
|
||||
중앙 정렬된 텍스트 출력 (줄바꿈 지원)
|
||||
|
||||
Returns:
|
||||
int: 다음 Y 위치
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 의존성
|
||||
|
||||
```
|
||||
pillow>=10.0.0 # 이미지 처리
|
||||
brother-ql>=0.9.4 # Brother QL 프린터 제어
|
||||
qrcode[pil]>=7.0 # QR 코드 생성
|
||||
```
|
||||
|
||||
### Brother QL 라이브러리 사용법
|
||||
|
||||
```python
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
from brother_ql.conversion import convert
|
||||
from brother_ql.backends.helpers import send
|
||||
|
||||
# 1. Raster 객체 생성
|
||||
qlr = BrotherQLRaster("QL-810W")
|
||||
|
||||
# 2. 이미지 변환 (29mm 라벨)
|
||||
instructions = convert(
|
||||
qlr=qlr,
|
||||
images=[pil_image],
|
||||
label='29',
|
||||
rotate='0',
|
||||
threshold=70.0,
|
||||
dither=False,
|
||||
compress=False,
|
||||
red=False,
|
||||
dpi_600=False,
|
||||
hq=True, # 고화질
|
||||
cut=True # 자동 절단
|
||||
)
|
||||
|
||||
# 3. 프린터 전송
|
||||
send(instructions, printer_identifier="tcp://192.168.0.168:9100")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
### 가로형 라벨 (800x306px)
|
||||
|
||||
Brother QL은 세로 방향이 기준이므로, 가로형 라벨은 **90도 회전 후 전송**해야 함:
|
||||
|
||||
```python
|
||||
# 가로형 이미지 생성 (800 x 306)
|
||||
label_img = create_wide_label(...)
|
||||
|
||||
# 90도 회전 (시계 반대방향)
|
||||
label_img_rotated = label_img.rotate(90, expand=True)
|
||||
|
||||
# 전송
|
||||
send(convert(..., images=[label_img_rotated], ...))
|
||||
```
|
||||
|
||||
### 폰트 경로
|
||||
|
||||
Windows: `C:/Windows/Fonts/malgunbd.ttf`
|
||||
Linux: `/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf`
|
||||
|
||||
폴백 처리 권장:
|
||||
```python
|
||||
font_paths = [
|
||||
"C:/Windows/Fonts/malgunbd.ttf",
|
||||
"/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf",
|
||||
]
|
||||
for path in font_paths:
|
||||
if os.path.exists(path):
|
||||
font = ImageFont.truetype(path, 32)
|
||||
break
|
||||
else:
|
||||
font = ImageFont.load_default()
|
||||
```
|
||||
|
||||
### 이미지 모드
|
||||
|
||||
Brother QL은 **1-bit (흑백)** 이미지 권장:
|
||||
```python
|
||||
image = Image.new("1", (width, height), 1) # 1 = 흰색
|
||||
# 또는
|
||||
image = image.convert('1')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 테이블 스키마 (SQLite)
|
||||
|
||||
### otc_label_presets
|
||||
|
||||
OTC 라벨 프리셋 저장용:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS otc_label_presets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
barcode TEXT UNIQUE NOT NULL,
|
||||
drug_name TEXT NOT NULL,
|
||||
effect TEXT,
|
||||
dosage_instruction TEXT,
|
||||
usage_tip TEXT,
|
||||
print_count INTEGER DEFAULT 0,
|
||||
last_printed_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 관련 문서
|
||||
|
||||
- `docs/PHARMACY_DB_GUIDE.md` - 약국 DB 조회 가이드
|
||||
- `docs/ENCODING_GUIDE.md` - 인코딩 문제 해결
|
||||
434
docs/OTC_LABEL_SYSTEM.md
Normal file
434
docs/OTC_LABEL_SYSTEM.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# OTC 용법 라벨 시스템
|
||||
|
||||
## 1. 시스템 개요
|
||||
|
||||
### OTC 라벨이란?
|
||||
OTC(Over-The-Counter) 약품 판매 시 부착하는 **용법·용량 안내 라벨**입니다.
|
||||
약사가 직접 설명하는 것 외에 시각적 보조 자료로, 복용 방법과 효능을 명확히 전달합니다.
|
||||
|
||||
### 전체 흐름
|
||||
```
|
||||
바코드 스캔 → POS 연동 → 웹 관리 페이지 → 미리보기 → Brother 프린터 출력
|
||||
```
|
||||
|
||||
1. **POS에서 바코드 스캔** → URL 호출 (`?barcode=...&name=...`)
|
||||
2. **관리 페이지 자동 로드** → 기존 프리셋이 있으면 채움
|
||||
3. **효능/용법 입력** → 실시간 미리보기
|
||||
4. **인쇄** → Brother QL-810W로 29mm 라벨 출력
|
||||
5. **프리셋 저장** → 다음 번엔 바코드만 스캔하면 바로 인쇄
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 시스템 구성도
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────────────────┐
|
||||
│ POS (PIT3000) │────▶│ Flask 서버 (port 7001) │
|
||||
│ 바코드 스캔 │ │ /admin/otc-labels │
|
||||
└─────────────────┘ └───────────┬──────────────────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌────────────┐ ┌──────────────┐
|
||||
│ SQLite │ │ MSSQL │ │ Brother │
|
||||
│ (프리셋 저장) │ │ (약품 정보) │ │ QL-810W │
|
||||
│ mileage.db │ │ PM_DRUG │ │ 192.168.0.168│
|
||||
└──────────────┘ └────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Flask 라우트 구조
|
||||
```
|
||||
/admin/otc-labels ← 관리 페이지 (HTML)
|
||||
/api/admin/otc-labels ← 프리셋 목록 조회 / 등록·수정
|
||||
/api/admin/otc-labels/<barcode> ← 단건 조회 / 삭제
|
||||
/api/admin/otc-labels/preview ← 미리보기 이미지 생성
|
||||
/api/admin/otc-labels/print ← 라벨 인쇄
|
||||
/api/admin/otc-labels/search-mssql ← MSSQL 약품 검색
|
||||
/api/otc-label-print/<barcode> ← 외부 GET 인쇄 (CORS 지원)
|
||||
/api/otc-label-check ← 프리셋 존재 여부 일괄 확인
|
||||
```
|
||||
|
||||
### 2.3 DB 연결
|
||||
| DB | 용도 | 연결 방식 |
|
||||
|---|---|---|
|
||||
| **SQLite** | 라벨 프리셋 저장 | `db_manager.get_sqlite_connection()` |
|
||||
| **MSSQL** | 약품 마스터 (CD_GOODS) | `db_manager.get_session('PM_DRUG')` |
|
||||
|
||||
- SQLite DB 경로: `backend/db/mileage.db`
|
||||
- MSSQL 인스턴스: `192.168.0.4\PM2014`
|
||||
|
||||
### 2.4 프린터 연동
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 프린터 | Brother QL-810W |
|
||||
| IP | 192.168.0.168 |
|
||||
| 포트 | 9100 (TCP) |
|
||||
| 용지 | 29mm 연속 라벨 |
|
||||
| 라이브러리 | `brother_ql` |
|
||||
|
||||
---
|
||||
|
||||
## 3. API 엔드포인트
|
||||
|
||||
### 3.1 관리 페이지
|
||||
```
|
||||
GET /admin/otc-labels
|
||||
GET /admin/otc-labels?barcode=8806436003118&name=노바손크림
|
||||
```
|
||||
- URL 파라미터로 바코드/이름 전달 시 자동 로드
|
||||
|
||||
---
|
||||
|
||||
### 3.2 프리셋 목록 조회
|
||||
```http
|
||||
GET /api/admin/otc-labels
|
||||
```
|
||||
|
||||
**응답 예시:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"count": 5,
|
||||
"labels": [
|
||||
{
|
||||
"id": 1,
|
||||
"barcode": "8806436003118",
|
||||
"drug_code": "DR001",
|
||||
"display_name": "노바손크림",
|
||||
"effect": "습진, 피부염",
|
||||
"dosage_instruction": "1일 2회, 환부에 얇게 도포",
|
||||
"usage_tip": "눈 주위 사용 금지",
|
||||
"use_wide_format": true,
|
||||
"print_count": 12,
|
||||
"last_printed_at": "2026-03-19 15:30:00",
|
||||
"created_at": "...",
|
||||
"updated_at": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 프리셋 단건 조회
|
||||
```http
|
||||
GET /api/admin/otc-labels/{barcode}
|
||||
```
|
||||
|
||||
**응답 예시:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"exists": true,
|
||||
"label": { /* 프리셋 데이터 */ }
|
||||
}
|
||||
```
|
||||
|
||||
**프리셋 없는 경우 (404):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "등록된 프리셋이 없습니다.",
|
||||
"exists": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 프리셋 등록/수정 (Upsert)
|
||||
```http
|
||||
POST /api/admin/otc-labels
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"barcode": "8806436003118",
|
||||
"drug_code": "DR001",
|
||||
"display_name": "노바손크림",
|
||||
"effect": "습진, 피부염",
|
||||
"dosage_instruction": "1일 2회, 환부에 얇게 도포",
|
||||
"usage_tip": "눈 주위 사용 금지",
|
||||
"use_wide_format": true
|
||||
}
|
||||
```
|
||||
|
||||
**필수 필드:** `barcode`
|
||||
**동작:** 바코드가 이미 존재하면 UPDATE, 없으면 INSERT
|
||||
|
||||
---
|
||||
|
||||
### 3.5 프리셋 삭제
|
||||
```http
|
||||
DELETE /api/admin/otc-labels/{barcode}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.6 미리보기 이미지 생성
|
||||
```http
|
||||
POST /api/admin/otc-labels/preview
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"drug_name": "노바손크림",
|
||||
"effect": "습진, 피부염",
|
||||
"dosage_instruction": "1일 2회, 환부에 얇게 도포",
|
||||
"usage_tip": "눈 주위 사용 금지"
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"preview_url": "data:image/png;base64,iVBORw0KGgo..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7 라벨 인쇄
|
||||
```http
|
||||
POST /api/admin/otc-labels/print
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"barcode": "8806436003118",
|
||||
"drug_name": "노바손크림",
|
||||
"effect": "습진, 피부염",
|
||||
"dosage_instruction": "1일 2회",
|
||||
"usage_tip": ""
|
||||
}
|
||||
```
|
||||
|
||||
**동작:** 인쇄 후 `print_count` 증가, `last_printed_at` 갱신
|
||||
|
||||
---
|
||||
|
||||
### 3.8 외부 GET 인쇄 (CORS 지원)
|
||||
```http
|
||||
GET /api/otc-label-print/{barcode}
|
||||
```
|
||||
|
||||
- **프리셋 있음** → 해당 데이터로 즉시 인쇄
|
||||
- **프리셋 없음** → 404 반환 (인쇄 안 함)
|
||||
- POS 등 외부 시스템에서 URL 호출로 바로 인쇄 가능
|
||||
|
||||
---
|
||||
|
||||
### 3.9 MSSQL 약품 검색
|
||||
```http
|
||||
GET /api/admin/otc-labels/search-mssql?q=노바손
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"count": 3,
|
||||
"drugs": [
|
||||
{
|
||||
"drug_code": "DR001",
|
||||
"barcode": "8806436003118",
|
||||
"goods_name": "노바손크림30g",
|
||||
"sale_price": 8500.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**쿼리 대상:**
|
||||
- `CD_GOODS.GoodsName` (약품명)
|
||||
- `CD_GOODS.Barcode` (바코드)
|
||||
- `CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE` (포장 단위 바코드)
|
||||
|
||||
---
|
||||
|
||||
### 3.10 프리셋 존재 여부 일괄 확인
|
||||
```http
|
||||
GET /api/otc-label-check?barcodes=8806436003118,8806436058613
|
||||
|
||||
# 또는
|
||||
|
||||
POST /api/otc-label-check
|
||||
Content-Type: application/json
|
||||
{
|
||||
"barcodes": ["8806436003118", "8806436058613"]
|
||||
}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 2,
|
||||
"found": 1,
|
||||
"results": {
|
||||
"8806436003118": true,
|
||||
"8806436058613": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. DB 스키마
|
||||
|
||||
### 4.1 테이블: `otc_label_presets` (SQLite)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| `id` | INTEGER | PK, 자동 증가 |
|
||||
| `barcode` | VARCHAR(20) | **UNIQUE**, 바코드 (실질적 PK) |
|
||||
| `drug_code` | VARCHAR(20) | MSSQL DrugCode (참조용) |
|
||||
| `display_name` | VARCHAR(100) | 표시 이름 (오버라이드) |
|
||||
| `effect` | VARCHAR(100) | 효능 (예: "치통, 두통") |
|
||||
| `dosage_instruction` | TEXT | 용법 (예: "1일 3회, 1회 1정") |
|
||||
| `usage_tip` | TEXT | 부가 설명 |
|
||||
| `use_wide_format` | BOOLEAN | 와이드 포맷 사용 여부 |
|
||||
| `print_count` | INTEGER | 인쇄 횟수 (통계) |
|
||||
| `last_printed_at` | DATETIME | 마지막 인쇄 시간 |
|
||||
| `created_at` | DATETIME | 생성 시간 |
|
||||
| `updated_at` | DATETIME | 수정 시간 |
|
||||
|
||||
**인덱스:**
|
||||
- `idx_otc_label_barcode` (barcode)
|
||||
- `idx_otc_label_drug_code` (drug_code)
|
||||
|
||||
---
|
||||
|
||||
### 4.2 MSSQL 테이블: `CD_GOODS` (약품 마스터)
|
||||
|
||||
검색 시 조회하는 테이블:
|
||||
|
||||
```sql
|
||||
SELECT TOP 20
|
||||
G.DrugCode,
|
||||
COALESCE(NULLIF(G.Barcode, ''),
|
||||
(SELECT TOP 1 CD_CD_BARCODE FROM CD_ITEM_UNIT_MEMBER WHERE DrugCode = G.DrugCode)
|
||||
) AS Barcode,
|
||||
G.GoodsName,
|
||||
G.Saleprice
|
||||
FROM CD_GOODS G
|
||||
WHERE G.GoodsName LIKE '%검색어%'
|
||||
OR G.Barcode LIKE '%검색어%'
|
||||
OR G.DrugCode IN (SELECT DrugCode FROM CD_ITEM_UNIT_MEMBER WHERE CD_CD_BARCODE LIKE '%검색어%')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 라벨 이미지 생성
|
||||
|
||||
### 5.1 이미지 사양
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 크기 | 800 × 306 px |
|
||||
| 색상 | 1-bit (흑백) |
|
||||
| 폰트 | 맑은 고딕 Bold (`malgunbd.ttf`) |
|
||||
|
||||
### 5.2 레이아웃
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ [효능 - 72pt, 굵게, 중앙 상단] │
|
||||
│ │
|
||||
│ □ 용법 - 40pt, 왼쪽 정렬 │
|
||||
│ [약품명 36pt] │
|
||||
│ □ 부가 설명 - 26pt ┌──────────┐ │
|
||||
│ │ 청춘약국 │ │
|
||||
│ └──────────┘ │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 인쇄 과정
|
||||
1. PIL로 이미지 생성 (가로 800 × 세로 306)
|
||||
2. 90도 회전 (Brother QL은 세로 기준)
|
||||
3. `brother_ql` 라이브러리로 래스터 변환
|
||||
4. TCP 9100 포트로 전송
|
||||
|
||||
---
|
||||
|
||||
## 6. 관련 파일 목록
|
||||
|
||||
### 6.1 핵심 파일
|
||||
| 파일 | 역할 |
|
||||
|---|---|
|
||||
| `backend/app.py` | Flask 라우트 (7730~8200줄) |
|
||||
| `backend/utils/otc_label_printer.py` | 이미지 생성 & 프린터 출력 |
|
||||
| `backend/templates/admin_otc_labels.html` | 관리 페이지 UI |
|
||||
| `backend/db/mileage_schema.sql` | 테이블 스키마 |
|
||||
| `backend/db/mileage.db` | SQLite DB |
|
||||
|
||||
### 6.2 app.py 내 주요 함수
|
||||
| 함수명 | 라인 | 설명 |
|
||||
|---|---|---|
|
||||
| `admin_otc_labels()` | 7735 | 관리 페이지 렌더링 |
|
||||
| `api_get_otc_labels()` | 7741 | 목록 조회 |
|
||||
| `api_get_otc_label()` | 7770 | 단건 조회 |
|
||||
| `api_upsert_otc_label()` | 7801 | 등록/수정 |
|
||||
| `api_delete_otc_label()` | 7848 | 삭제 |
|
||||
| `api_preview_otc_label()` | 7868 | 미리보기 |
|
||||
| `api_print_otc_label()` | 7900 | 인쇄 |
|
||||
| `api_otc_label_print_by_barcode()` | 7948 | GET 인쇄 |
|
||||
| `api_otc_label_check()` | 8039 | 일괄 확인 |
|
||||
| `api_search_mssql_drug()` | 8117 | MSSQL 검색 |
|
||||
|
||||
### 6.3 otc_label_printer.py 함수
|
||||
| 함수명 | 설명 |
|
||||
|---|---|
|
||||
| `create_otc_label_image()` | 라벨 이미지 생성 (PIL) |
|
||||
| `print_otc_label()` | Brother QL로 인쇄 |
|
||||
| `generate_preview_image()` | Base64 미리보기 생성 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 트러블슈팅
|
||||
|
||||
### 프린터 연결 테스트
|
||||
```python
|
||||
import socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(3)
|
||||
result = sock.connect_ex(("192.168.0.168", 9100))
|
||||
print("OK" if result == 0 else f"FAIL: {result}")
|
||||
sock.close()
|
||||
```
|
||||
|
||||
### 모듈 로드 실패
|
||||
`OTC_LABEL_AVAILABLE = False` 로그 발생 시:
|
||||
- `brother_ql` 설치 확인: `pip install brother_ql`
|
||||
- Pillow 설치 확인: `pip install Pillow`
|
||||
|
||||
### 폰트 깨짐
|
||||
- Windows: `C:/Windows/Fonts/malgunbd.ttf` 존재 확인
|
||||
- 대체 폰트: NanumGothicBold 등
|
||||
|
||||
---
|
||||
|
||||
## 8. 사용 예시
|
||||
|
||||
### POS에서 라벨 페이지 열기
|
||||
```
|
||||
https://mile.0bin.in/admin/otc-labels?barcode=8806436003118&name=노바손크림
|
||||
```
|
||||
|
||||
### 외부 시스템에서 바로 인쇄
|
||||
```bash
|
||||
curl https://mile.0bin.in/api/otc-label-print/8806436003118
|
||||
```
|
||||
|
||||
### 프리셋 일괄 등록 (스크립트)
|
||||
```python
|
||||
import requests
|
||||
|
||||
labels = [
|
||||
{"barcode": "8806436003118", "display_name": "노바손크림", "effect": "습진", "dosage_instruction": "1일 2회"},
|
||||
{"barcode": "8806436058613", "display_name": "게보린", "effect": "두통", "dosage_instruction": "1회 1정"},
|
||||
]
|
||||
|
||||
for label in labels:
|
||||
requests.post("https://mile.0bin.in/api/admin/otc-labels", json=label)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*문서 작성: 2026-03-19*
|
||||
263
docs/환산계수.md
Normal file
263
docs/환산계수.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# 건조시럽 환산계수 시스템
|
||||
|
||||
> 작성일: 2026-03-19
|
||||
> 작성자: 용림 🐉
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
건조시럽(dry syrup)은 물로 희석하여 복용하는 시럽 형태의 의약품입니다.
|
||||
**환산계수(conversion_factor)**를 사용하여 복용량(mL)을 실제 분말량(g)으로 변환합니다.
|
||||
|
||||
### 계산 예시
|
||||
```
|
||||
오구멘틴듀오시럽 228mg/5ml
|
||||
├─ 환산계수: 0.11
|
||||
├─ 총량: 120mL
|
||||
└─ 필요 분말량: 120 × 0.11 = 13.2g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 데이터베이스 정보
|
||||
|
||||
### PostgreSQL 연결
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| **Host** | 192.168.0.39 |
|
||||
| **Port** | 5432 |
|
||||
| **Database** | label10 |
|
||||
| **User** | admin |
|
||||
| **Password** | trajet6640 |
|
||||
|
||||
### Connection String
|
||||
```
|
||||
postgresql://admin:trajet6640@192.168.0.39:5432/label10
|
||||
```
|
||||
|
||||
### Python 연결 코드
|
||||
```python
|
||||
import psycopg2
|
||||
|
||||
conn = psycopg2.connect(
|
||||
host='192.168.0.39',
|
||||
port=5432,
|
||||
database='label10',
|
||||
user='admin',
|
||||
password='trajet6640'
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 테이블 스키마
|
||||
|
||||
### drysyrup 테이블
|
||||
|
||||
| 컬럼명 | 타입 | 설명 |
|
||||
|--------|------|------|
|
||||
| `idx` | INTEGER | PK, 자동증가 |
|
||||
| `ingredient_code` | VARCHAR | 성분코드 (MSSQL SUNG_CODE와 매칭) |
|
||||
| `ingredient_name` | VARCHAR | 성분명 |
|
||||
| `product_name` | VARCHAR | 대표 제품명 |
|
||||
| `post_prep_amount` | VARCHAR | 조제 후 농도 (예: 25mg/ml) |
|
||||
| `main_ingredient_amt` | VARCHAR | 주성분량 (예: 0.75g/16.7g) |
|
||||
| `conversion_factor` | DOUBLE PRECISION | **환산계수** (mL → g) |
|
||||
| `storage_conditions` | VARCHAR | 보관조건 (냉장, 상온 등) |
|
||||
| `expiration_date` | VARCHAR | 조제 후 유효기간 |
|
||||
|
||||
### 매핑 관계
|
||||
```
|
||||
MSSQL (PIT3000) PostgreSQL (label10)
|
||||
───────────────── ────────────────────
|
||||
PM_DRUG.CD_GOODS drysyrup
|
||||
└─ SUNG_CODE ──────▶ └─ ingredient_code
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터 샘플 (23건)
|
||||
|
||||
| idx | ingredient_code | 성분명 | 제품명 | 환산계수 | 보관 | 유효기간 |
|
||||
|-----|-----------------|--------|--------|----------|------|----------|
|
||||
| 18 | 125333ASY | 세파드록실수화물 | 보령듀리세프 125mg/5ml | 0.557 | 냉장 | 14일 |
|
||||
| 19 | 125332ASY | 세파드록실수화물 | 보령듀리세프 250mg/5ml | 0.557 | 냉장 | 14일 |
|
||||
| 20 | 125237ASY | 세파클러수화물 | 크로세프 | 0.667 | 냉장 | 14일 |
|
||||
| 21 | 128931ASY | 세푸록심악세틸 | 올세프 | 1.0 | 25℃이하 | 10일 |
|
||||
| 22 | 127931ASY | 세프포독심프록세틸 | 포독스 | 0.2 | 냉장 | 14일 |
|
||||
| 23 | 128030ASY | 세프프로질수화물 | 세프질시럽 | 0.5 | 냉장 | 14일 |
|
||||
| 24 | 108130ASY | 아목시실린수화물 | 파목신시럽 | 0.775 | 냉장 | 14일 |
|
||||
| 25 | 535000ASY | 아목시실린+클라불란산 | 오구멘틴듀오 228mg/5ml | **0.11** | 냉장 | 7일 |
|
||||
| 26 | 536300ASY | 아목시실린+클라불란산 | 아목클란네오시럽 | 0.22 | 냉장 | 7일 |
|
||||
| 27 | 112732ASY | 아지트로마이신수화물 | 지스로맥스 | 0.867 | 상온 | 5일 |
|
||||
|
||||
---
|
||||
|
||||
## 5. API 엔드포인트
|
||||
|
||||
### 환산계수 조회
|
||||
|
||||
```
|
||||
GET /api/drug-info/conversion-factor/<sung_code>
|
||||
```
|
||||
|
||||
#### 요청 예시
|
||||
```bash
|
||||
curl https://mile.0bin.in/api/drug-info/conversion-factor/535000ASY
|
||||
```
|
||||
|
||||
#### 응답 (성공)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"sung_code": "535000ASY",
|
||||
"conversion_factor": 0.11,
|
||||
"ingredient_name": "아목시실린수화물·클라불란산칼륨",
|
||||
"product_name": "일성오구멘틴듀오시럽 228mg/5ml"
|
||||
}
|
||||
```
|
||||
|
||||
#### 응답 (데이터 없음)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"sung_code": "NOTEXIST",
|
||||
"conversion_factor": null,
|
||||
"ingredient_name": null,
|
||||
"product_name": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 쿼리 예시
|
||||
|
||||
### 환산계수 조회
|
||||
```sql
|
||||
SELECT conversion_factor, ingredient_name, product_name,
|
||||
storage_conditions, expiration_date
|
||||
FROM drysyrup
|
||||
WHERE ingredient_code = '535000ASY';
|
||||
```
|
||||
|
||||
### 전체 목록 조회
|
||||
```sql
|
||||
SELECT * FROM drysyrup ORDER BY idx;
|
||||
```
|
||||
|
||||
### 특정 성분 검색
|
||||
```sql
|
||||
SELECT * FROM drysyrup
|
||||
WHERE ingredient_name LIKE '%아목시실린%';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Python 사용 예시
|
||||
|
||||
### 환산계수 조회 함수
|
||||
```python
|
||||
import psycopg2
|
||||
|
||||
def get_conversion_factor(sung_code):
|
||||
"""성분코드로 환산계수 조회"""
|
||||
conn = psycopg2.connect(
|
||||
host='192.168.0.39',
|
||||
port=5432,
|
||||
database='label10',
|
||||
user='admin',
|
||||
password='trajet6640'
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT conversion_factor, ingredient_name, product_name,
|
||||
storage_conditions, expiration_date
|
||||
FROM drysyrup
|
||||
WHERE ingredient_code = %s
|
||||
""", (sung_code,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if row:
|
||||
return {
|
||||
'conversion_factor': row[0],
|
||||
'ingredient_name': row[1],
|
||||
'product_name': row[2],
|
||||
'storage_conditions': row[3],
|
||||
'expiration_date': row[4]
|
||||
}
|
||||
return None
|
||||
|
||||
# 사용 예시
|
||||
result = get_conversion_factor('535000ASY')
|
||||
print(result)
|
||||
# {'conversion_factor': 0.11, 'ingredient_name': '아목시실린...', ...}
|
||||
```
|
||||
|
||||
### 분말량 계산 함수
|
||||
```python
|
||||
def calculate_powder_amount(sung_code, total_ml):
|
||||
"""총 mL로 필요한 분말량(g) 계산"""
|
||||
data = get_conversion_factor(sung_code)
|
||||
if data and data['conversion_factor']:
|
||||
return round(total_ml * data['conversion_factor'], 2)
|
||||
return None
|
||||
|
||||
# 사용 예시
|
||||
powder = calculate_powder_amount('535000ASY', 120)
|
||||
print(f"필요 분말량: {powder}g") # 필요 분말량: 13.2g
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 관련 파일
|
||||
|
||||
| 파일 | 위치 | 설명 |
|
||||
|------|------|------|
|
||||
| app.py | `backend/app.py` | Flask API 라우트 |
|
||||
| DRYSYRUP_CONVERSION.md | `docs/` | 기존 문서 |
|
||||
|
||||
### Flask 라우트 위치
|
||||
```python
|
||||
# backend/app.py
|
||||
@app.route('/api/drug-info/conversion-factor/<sung_code>')
|
||||
def get_drug_conversion_factor(sung_code):
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 아키텍처
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 클라이언트 │
|
||||
│ (POS, 라벨 프린터, 웹 UI) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Flask Backend (7001) │
|
||||
│ GET /api/drug-info/conversion-factor/<sung_code> │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ MSSQL (PIT3000) │ │ PostgreSQL │
|
||||
│ 192.168.0.4 │ │ 192.168.0.39:5432 │
|
||||
├─────────────────────┤ ├─────────────────────┤
|
||||
│ PM_DRUG.CD_GOODS │ │ label10.drysyrup │
|
||||
│ └─ SUNG_CODE ─────┼──────▶│ └─ ingredient_code│
|
||||
│ └─ GoodsName │ │ └─ conversion_factor
|
||||
│ └─ DrugCode │ │ └─ storage_conditions
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*총 23개 건조시럽 환산계수 등록됨*
|
||||
Reference in New Issue
Block a user