- product_master 테이블: 제품 마스터 (바코드, 이름, 성분, 태그) - product_categories: 제품 카테고리 22개 (진통제, 소화제 등) - product_category_mapping: 다대다 매핑 (하나의 제품이 여러 카테고리) - disease_codes: 질병 코드 ICD-10 12개 - disease_product_mapping: 질병-제품 매핑 - 샘플 제품 3개 추가 (탁센, 베아제, 마그비맥스) - BARCODE 컬럼 95.79% 보유율 확인 - 온톨로지 기반 추천 시스템 설계 문서 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
450 lines
11 KiB
Markdown
450 lines
11 KiB
Markdown
# 청춘약국 마일리지 QR 시스템
|
|
|
|
## 프로젝트 개요
|
|
약국 POS 시스템과 연동하여 고객이 QR 코드를 스캔하면 자동으로 마일리지가 적립되는 시스템
|
|
|
|
---
|
|
|
|
## 🌐 접속 URL (외부 접속 가능)
|
|
|
|
### 실시간 서버 (리버스 프록시)
|
|
- **메인 페이지**: https://mile.0bin.in/
|
|
- **간편 적립**: https://mile.0bin.in/claim?t={토큰}
|
|
- **마이페이지**: https://mile.0bin.in/my-page
|
|
- **관리자 페이지**: https://mile.0bin.in/admin
|
|
|
|
### 로컬 서버
|
|
- **로컬 주소**: http://localhost:7001/
|
|
- **서버 포트**: 7001
|
|
- **실행 명령**: `cd backend && python app.py`
|
|
|
|
---
|
|
|
|
## 📂 프로젝트 구조
|
|
|
|
```
|
|
pharmacy-pos-qr-system/
|
|
├── backend/
|
|
│ ├── app.py # Flask 웹 서버 (포트 7001)
|
|
│ ├── db/
|
|
│ │ ├── dbsetup.py # DB 연결 관리자 (MSSQL + SQLite)
|
|
│ │ ├── mileage_schema.sql # SQLite 스키마
|
|
│ │ └── mileage.db # SQLite 데이터베이스 파일
|
|
│ ├── utils/
|
|
│ │ ├── qr_token_generator.py # QR 토큰 생성 (SHA256 해시)
|
|
│ │ └── qr_label_printer.py # QR 라벨 인쇄 (Brother QL-810W)
|
|
│ ├── gui/
|
|
│ │ └── pos_sales_gui.py # POS 판매 GUI (PyQt5)
|
|
│ ├── templates/ # Flask HTML 템플릿
|
|
│ │ ├── claim_form.html # 간편 적립 페이지
|
|
│ │ ├── my_page.html # 마이페이지
|
|
│ │ ├── my_page_login.html # 마이페이지 로그인
|
|
│ │ ├── admin.html # 관리자 대시보드
|
|
│ │ └── error.html # 에러 페이지
|
|
│ └── test_integration.py # 통합 테스트 스크립트
|
|
└── CLAUDECODE.md # 이 문서
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 핵심 기능
|
|
|
|
### Phase 2: QR 라벨 인쇄 ✅
|
|
1. **토큰 생성**: SHA256 해시 기반, 30일 유효기간
|
|
2. **QR 코드**: 200x200px, 높은 오류 복원력 (ERROR_CORRECT_H)
|
|
3. **URL 최적화**: 약 68자 (빠른 스캔)
|
|
- 예시: `https://pharmacy.example.com/claim?t=TEST20260123145834:795d07519294`
|
|
4. **Brother QL-810W 프린터**: 29mm 가로형 라벨 (800x306px)
|
|
5. **POS GUI 통합**: QR 생성 버튼, 미리보기 모드
|
|
|
|
### Phase 3: 간편 적립 웹앱 ✅
|
|
1. **간편 가입/적립**: 전화번호 + 이름만 입력
|
|
2. **자동 회원 생성**: 신규 사용자 자동 등록
|
|
3. **마일리지 원장**: 모든 적립 내역 추적
|
|
4. **마이페이지**: 전화번호로 포인트 조회
|
|
5. **관리자 페이지**: 전체 사용자/적립 현황 조회
|
|
6. **거래 세부 조회**: MSSQL 연동으로 판매 상품 상세 확인 (모달 팝업)
|
|
7. **모던 UI**: Noto Sans KR 폰트, 반응형 디자인
|
|
8. **POS GUI 통합**: SQLite 적립 사용자 정보 표시 (이름, 전화번호)
|
|
9. **실시간 동기화**: 30초마다 자동 새로고침으로 적립 상태 실시간 반영
|
|
|
|
---
|
|
|
|
## 📊 데이터베이스
|
|
|
|
### SQLite (mileage.db)
|
|
위치: `backend/db/mileage.db`
|
|
|
|
#### 1. users (사용자)
|
|
```sql
|
|
- id: 사용자 ID (자동 증가)
|
|
- nickname: 이름
|
|
- phone: 전화번호 (하이픈 제거, 10-11자리)
|
|
- mileage_balance: 포인트 잔액
|
|
- created_at: 가입일
|
|
```
|
|
|
|
#### 2. claim_tokens (QR 토큰)
|
|
```sql
|
|
- id: 토큰 ID
|
|
- transaction_id: 거래 번호 (UNIQUE)
|
|
- token_hash: SHA256 해시 (UNIQUE)
|
|
- total_amount: 판매 금액
|
|
- claimable_points: 적립 포인트 (3%)
|
|
- expires_at: 만료일 (30일)
|
|
- claimed_at: 적립 완료 시간
|
|
- claimed_by_user_id: 적립한 사용자
|
|
```
|
|
|
|
#### 3. mileage_ledger (마일리지 원장)
|
|
```sql
|
|
- id: 원장 ID
|
|
- user_id: 사용자 ID
|
|
- transaction_id: 거래 번호
|
|
- points: 포인트 변동량
|
|
- balance_after: 변동 후 잔액
|
|
- reason: 사유 (CLAIM, USE 등)
|
|
- description: 상세 설명
|
|
```
|
|
|
|
### MSSQL (POS 시스템 - PIT3000)
|
|
|
|
위치: `PM_PRES` 데이터베이스
|
|
|
|
#### SALE_MAIN (판매 헤더)
|
|
|
|
```sql
|
|
- SL_NO_order: 거래 번호 (Primary Key)
|
|
- SL_DT_appl: 거래 날짜
|
|
- InsertTime: 거래 일시
|
|
- SL_MY_total: 총액 (할인 전)
|
|
- SL_MY_discount: 할인 금액
|
|
- SL_MY_sale: 판매 금액 (할인 후)
|
|
- SL_MY_credit: 외상 금액
|
|
- SL_MY_recive: 수금 금액
|
|
- SL_NM_custom: 고객명
|
|
```
|
|
|
|
#### SALE_SUB (판매 상세)
|
|
|
|
```sql
|
|
- SL_NO_order: 거래 번호 (Foreign Key)
|
|
- DrugCode: 약품 코드
|
|
- BARCODE: 제품 바코드 (nvarchar(20)) ⭐ 95.79% 보유율
|
|
- SL_NM_item: 수량 (decimal)
|
|
- SL_INPUT_PRICE: 입력 단가
|
|
- SL_TOTAL_PRICE: 합계 금액
|
|
```
|
|
|
|
**바코드 통계**:
|
|
|
|
- 전체 제품 수: 3,120개
|
|
- 바코드 종류: 3,307개
|
|
- 바코드 보유율: **95.79%** (174,327건 / 181,985건)
|
|
- 활용: AI 기반 제품 태깅, 온톨로지 구축, 개인화 추천
|
|
|
|
#### CD_GOODS (약품 마스터 - PM_DRUG 데이터베이스)
|
|
|
|
```sql
|
|
- DrugCode: 약품 코드 (Primary Key)
|
|
- GoodsName: 약품명
|
|
```
|
|
|
|
**JOIN 예시**:
|
|
```sql
|
|
SELECT
|
|
S.DrugCode,
|
|
ISNULL(G.GoodsName, '(약품명 없음)') AS goods_name,
|
|
S.SL_NM_item AS quantity,
|
|
S.SL_INPUT_PRICE AS price,
|
|
S.SL_TOTAL_PRICE AS total
|
|
FROM SALE_SUB S
|
|
LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode
|
|
WHERE S.SL_NO_order = :transaction_id
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 보안 정책
|
|
|
|
1. **토큰 보안**
|
|
- `token_raw`: QR 코드에만 포함 (DB 저장 X)
|
|
- `token_hash`: SHA256 해시만 DB 저장 (64자)
|
|
- `nonce`: 6바이트 암호학적 난수 (12자 hex)
|
|
- 동일 거래도 매번 다른 토큰 생성
|
|
|
|
2. **1회성 보장**
|
|
- `claimed_at IS NULL` 검증
|
|
- `UNIQUE(transaction_id)` 제약
|
|
|
|
3. **만료 기간**
|
|
- 30일 후 자동 만료
|
|
- `expires_at` 필드로 관리
|
|
|
|
---
|
|
|
|
## 🧪 테스트 방법
|
|
|
|
### 1. QR 토큰 생성 및 라벨 테스트
|
|
```bash
|
|
cd backend
|
|
python test_integration.py
|
|
```
|
|
|
|
출력 예시:
|
|
```
|
|
[OK] QR URL: https://pharmacy.example.com/claim?t=TEST20260123145834:795d07519294
|
|
[OK] URL 길이: 68 문자
|
|
[OK] 적립 포인트: 2250P
|
|
[OK] 이미지 저장: backend/samples/temp/qr_receipt_TEST20260123145834_20260123_145834.png
|
|
```
|
|
|
|
### 2. Flask 서버 실행
|
|
```bash
|
|
cd backend
|
|
python app.py
|
|
```
|
|
|
|
서버 접속: http://localhost:7001/
|
|
|
|
### 3. 전체 흐름 테스트
|
|
1. **QR 생성**: `test_integration.py` 실행
|
|
2. **웹앱 접속**: 생성된 URL로 이동
|
|
3. **간편 적립**: 전화번호 + 이름 입력
|
|
4. **마이페이지**: 적립 내역 확인
|
|
5. **관리자 페이지**: 전체 현황 확인
|
|
|
|
---
|
|
|
|
## 📝 API 엔드포인트
|
|
|
|
### GET /
|
|
메인 페이지 (안내)
|
|
|
|
### GET /claim?t={transaction_id}:{nonce}
|
|
간편 적립 페이지
|
|
- 토큰 검증
|
|
- 구매 금액 및 적립 포인트 표시
|
|
- 전화번호/이름 입력 폼
|
|
|
|
### POST /api/claim
|
|
마일리지 적립 API
|
|
```json
|
|
{
|
|
"transaction_id": "거래번호",
|
|
"nonce": "12자 hex",
|
|
"phone": "전화번호",
|
|
"name": "이름"
|
|
}
|
|
```
|
|
|
|
응답:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "2250P 적립 완료!",
|
|
"points": 2250,
|
|
"balance": 2250,
|
|
"is_new_user": true
|
|
}
|
|
```
|
|
|
|
### GET /my-page
|
|
마이페이지 (로그인)
|
|
|
|
### GET /my-page?phone={전화번호}
|
|
마이페이지 (포인트 조회)
|
|
|
|
### GET /admin
|
|
관리자 대시보드
|
|
- 총 가입자 수
|
|
- 누적 포인트 잔액
|
|
- QR 발행 건수
|
|
- 적립 완료율
|
|
- 최근 가입 사용자 (20명)
|
|
- 최근 적립 내역 (50건)
|
|
- 최근 QR 발행 내역 (20건)
|
|
|
|
### GET /admin/transaction/{transaction_id}
|
|
|
|
거래 세부 내역 조회 API (MSSQL 연동)
|
|
|
|
응답:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"transaction": {
|
|
"id": "20260123000042",
|
|
"date": "2026-01-23 14:30:15",
|
|
"customer_name": "홍길동",
|
|
"total_amount": 50000,
|
|
"discount": 5000,
|
|
"sale_amount": 45000,
|
|
"credit": 0,
|
|
"received": 45000
|
|
},
|
|
"items": [
|
|
{
|
|
"code": "A001234",
|
|
"name": "타이레놀 500mg",
|
|
"qty": 2,
|
|
"price": 5000,
|
|
"total": 10000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 UI/UX 특징
|
|
|
|
### 디자인 시스템
|
|
- **폰트**: Noto Sans KR (Google Fonts)
|
|
- **컬러**:
|
|
- Primary: `#6366f1` (인디고)
|
|
- Secondary: `#8b5cf6` (바이올렛)
|
|
- Background: `#f5f7fa`
|
|
- Text: `#212529`, `#495057`, `#868e96`
|
|
- **Border Radius**: 14px ~ 24px
|
|
- **그림자**: `0 4px 24px rgba(0, 0, 0, 0.06)`
|
|
|
|
### 애니메이션
|
|
- **체크마크**: stroke-dasharray 애니메이션
|
|
- **버튼**: scale(0.98) on active
|
|
- **성공 아이콘**: scaleIn + bounce
|
|
|
|
### 반응형
|
|
- 모바일 최적화 (min-width: 420px)
|
|
- 테이블 스크롤 지원
|
|
- Touch-friendly 버튼 크기
|
|
|
|
---
|
|
|
|
## ⚙️ 설정 파일
|
|
|
|
### QR 토큰 생성 (qr_token_generator.py)
|
|
```python
|
|
MILEAGE_RATE = 0.03 # 3% 적립
|
|
TOKEN_EXPIRY_DAYS = 30 # 30일 유효기간
|
|
QR_BASE_URL = "https://mile.0bin.in/claim"
|
|
```
|
|
|
|
### 라벨 프린터 (qr_label_printer.py)
|
|
```python
|
|
PRINTER_IP = "192.168.0.168"
|
|
PRINTER_PORT = 9100
|
|
PRINTER_MODEL = "QL-810W"
|
|
LABEL_TYPE = "29" # 29mm 라벨
|
|
```
|
|
|
|
### Flask 서버 (app.py)
|
|
```python
|
|
app.run(host='0.0.0.0', port=7001, debug=True)
|
|
```
|
|
|
|
---
|
|
|
|
## 🔧 유지보수
|
|
|
|
### DB 백업
|
|
```bash
|
|
# SQLite 백업
|
|
sqlite3 backend/db/mileage.db ".backup 'backup/mileage_backup_20260123.db'"
|
|
|
|
# 또는 단순 복사
|
|
cp backend/db/mileage.db backup/mileage_backup_20260123.db
|
|
```
|
|
|
|
### DB 조회
|
|
```bash
|
|
# SQLite 연결
|
|
sqlite3 backend/db/mileage.db
|
|
|
|
# 사용자 조회
|
|
SELECT * FROM users;
|
|
|
|
# 적립 내역 조회
|
|
SELECT * FROM mileage_ledger;
|
|
|
|
# QR 토큰 조회
|
|
SELECT * FROM claim_tokens;
|
|
```
|
|
|
|
### 로그 확인
|
|
Flask 서버는 콘솔에 로그를 출력합니다:
|
|
```
|
|
[DB Manager] SQLite 기존 DB 연결: E:\cclabel\pharmacy-pos-qr-system\backend\db\mileage.db
|
|
* Running on http://0.0.0.0:7001
|
|
```
|
|
|
|
---
|
|
|
|
## 🚨 트러블슈팅
|
|
|
|
### 1. QR 코드가 인식되지 않을 때
|
|
- QR 크기: 200x200px (충분히 큼)
|
|
- Error correction: H (30% 복원)
|
|
- URL 길이: 68자 (최적화됨)
|
|
- 조명 확인, 카메라 초점 확인
|
|
|
|
### 2. 적립이 안 될 때
|
|
- 토큰 만료 확인 (30일)
|
|
- 이미 적립된 토큰인지 확인 (`claimed_at`)
|
|
- DB 연결 상태 확인
|
|
|
|
### 3. 프린터 연결 안 될 때
|
|
- IP 주소 확인: `192.168.0.168`
|
|
- 네트워크 연결 확인
|
|
- 프린터 전원 확인
|
|
|
|
### 4. 한글이 깨질 때
|
|
- 폰트 경로 확인: `C:\Windows\Fonts\malgunbd.ttf`
|
|
- 폴백 폰트 사용 여부 로그 확인
|
|
|
|
---
|
|
|
|
## 📌 TODO / 개선 사항
|
|
|
|
### Phase 4 (선택)
|
|
- [ ] 카카오 로그인 연동 (customer_identities 테이블 활용)
|
|
- [ ] SMS 알림 (적립 완료 시 문자 발송)
|
|
- [ ] 실제 도메인 적용 (QR_BASE_URL 변경)
|
|
- [ ] HTTPS 적용 (SSL 인증서)
|
|
- [ ] 포인트 사용 기능 (결제 시 차감)
|
|
- [ ] 관리자 로그인 (비밀번호 보호)
|
|
- [ ] 통계 그래프 (Chart.js)
|
|
- [ ] 엑셀 내보내기 (사용자/적립 내역)
|
|
|
|
---
|
|
|
|
## 📞 문의
|
|
|
|
- **프로젝트**: 청춘약국 마일리지 QR 시스템
|
|
- **개발 기간**: 2026년 1월
|
|
- **기술 스택**: Python, Flask, SQLite, PyQt5, Brother QL
|
|
- **접속 URL**: https://mile.0bin.in/
|
|
|
|
---
|
|
|
|
## 📚 참고 자료
|
|
|
|
### 주요 라이브러리
|
|
- Flask: 웹 프레임워크
|
|
- SQLite3: 경량 데이터베이스
|
|
- qrcode: QR 코드 생성
|
|
- Pillow: 이미지 처리
|
|
- brother_ql: Brother QL 프린터 제어
|
|
- PyQt5: POS GUI
|
|
|
|
### 외부 링크
|
|
- Flask 문서: https://flask.palletsprojects.com/
|
|
- Brother QL Python: https://github.com/pklaus/brother_ql
|
|
- QRCode 문서: https://pypi.org/project/qrcode/
|
|
|
|
---
|
|
|
|
**마지막 업데이트**: 2026-01-23
|
|
**버전**: Phase 3 완료 (간편 적립 + 관리자 페이지 + 거래 세부 조회)
|