Files
pharmacy-pos-qr-system/docs/OTC_LABEL_SYSTEM.md

12 KiB
Raw Blame History

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 프리셋 목록 조회

GET /api/admin/otc-labels

응답 예시:

{
  "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 프리셋 단건 조회

GET /api/admin/otc-labels/{barcode}

응답 예시:

{
  "success": true,
  "exists": true,
  "label": { /* 프리셋 데이터 */ }
}

프리셋 없는 경우 (404):

{
  "success": false,
  "error": "등록된 프리셋이 없습니다.",
  "exists": false
}

3.4 프리셋 등록/수정 (Upsert)

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 프리셋 삭제

DELETE /api/admin/otc-labels/{barcode}

3.6 미리보기 이미지 생성

POST /api/admin/otc-labels/preview
Content-Type: application/json

{
  "drug_name": "노바손크림",
  "effect": "습진, 피부염",
  "dosage_instruction": "1일 2회, 환부에 얇게 도포",
  "usage_tip": "눈 주위 사용 금지"
}

응답:

{
  "success": true,
  "preview_url": "data:image/png;base64,iVBORw0KGgo..."
}

3.7 라벨 인쇄

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 지원)

GET /api/otc-label-print/{barcode}
  • 프리셋 있음 → 해당 데이터로 즉시 인쇄
  • 프리셋 없음 → 404 반환 (인쇄 안 함)
  • POS 등 외부 시스템에서 URL 호출로 바로 인쇄 가능

3.9 MSSQL 약품 검색

GET /api/admin/otc-labels/search-mssql?q=노바손

응답:

{
  "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 프리셋 존재 여부 일괄 확인

GET /api/otc-label-check?barcodes=8806436003118,8806436058613

# 또는

POST /api/otc-label-check
Content-Type: application/json
{
  "barcodes": ["8806436003118", "8806436058613"]
}

응답:

{
  "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 (약품 마스터)

검색 시 조회하는 테이블:

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. 트러블슈팅

프린터 연결 테스트

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=노바손크림

외부 시스템에서 바로 인쇄

curl https://mile.0bin.in/api/otc-label-print/8806436003118

프리셋 일괄 등록 (스크립트)

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