feat: 알림톡 발송 로그 시스템 + 현영 표시 + 문서화
- 알림톡 발송 로그: alimtalk_logs SQLite 테이블 + DB 자동 기록 - /admin/alimtalk 페이지: 서버 로그, NHN Cloud 내역 조회, 수동 발송 테스트 - 적립일시 포맷 수정: %Y-%m-%d %H:%M (16자 초과) → %m/%d %H:%M (11자) - POS GUI 현금영수증(현영) 표시: 청록색 볼드 - 결제수납구조.md: CD_SUNAB/PS_main/SALE_MAIN 3테이블 관계 문서 - 실행구조.md: Flask 서버 + Qt GUI 실행 가이드 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
277
docs/결제수납구조.md
277
docs/결제수납구조.md
@@ -1,22 +1,217 @@
|
||||
# PIT3000 결제/수납/할인 데이터 구조
|
||||
# PIT3000 판매/조제/수납 데이터 구조
|
||||
|
||||
## 핵심 테이블 관계
|
||||
|
||||
```
|
||||
SALE_MAIN (판매)
|
||||
└── SL_NO_order (PK, 주문번호)
|
||||
CD_SUNAB (수납/결제) ─── 모든 거래의 결제 기록 (130건/일 기준)
|
||||
│
|
||||
├── PS_main (처방접수) ─── 조제 건만 (89건/일 기준)
|
||||
│ │ 조인: PS_main.PreSerial = CD_SUNAB.PRESERIAL
|
||||
│ │ 조인: PS_main.Indate = CD_SUNAB.INDATE
|
||||
│ │
|
||||
│ ├── PS_sub_hosp (처방 의약품 상세)
|
||||
│ └── PS_sub_pharm (조제 의약품 상세)
|
||||
│
|
||||
└── SALE_MAIN (OTC 판매) ─── OTC 직접 판매만 (39건/일 기준)
|
||||
│ 조인: SALE_MAIN.SL_NO_order = CD_SUNAB.PRESERIAL
|
||||
│
|
||||
├── SALE_SUB (품목 상세) — SL_NO_order로 조인
|
||||
│
|
||||
└── CD_SUNAB (수납/결제) — CD_SUNAB.PRESERIAL = SALE_MAIN.SL_NO_order
|
||||
└── SALE_SUB (판매 품목 상세) ─── SL_NO_order로 조인
|
||||
```
|
||||
|
||||
**주의**: `CD_SUNAB.PRESERIAL`은 `SALE_MAIN.SL_NO_order`(주문번호)와 매칭됨.
|
||||
`SALE_MAIN.PRESERIAL`(처방번호)과는 다른 키임.
|
||||
## 테이블별 역할
|
||||
|
||||
### 1. CD_SUNAB — 수납/결제 (모든 거래 포함)
|
||||
- **역할**: 조제 + OTC 모든 거래의 결제/수납 기록
|
||||
- **1주문 = 1행** (복수행 없음)
|
||||
- **키**: `PRESERIAL` (주문번호), `INDATE` (수납일)
|
||||
- **건수**: 하루 약 130건 (조제 91 + OTC 39)
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `PRESERIAL` | 주문번호 (PS_main.PreSerial 또는 SALE_MAIN.SL_NO_order와 매칭) |
|
||||
| `INDATE` | 수납일 (YYYYMMDD) |
|
||||
| `DAY_SERIAL` | 일련번호 |
|
||||
| `CUSCODE` | 고객코드 |
|
||||
| `ETC_CARD` | 조제 카드결제 금액 |
|
||||
| `ETC_CASH` | 조제 현금결제 금액 |
|
||||
| `ETC_PAPER` | 조제 외상 금액 |
|
||||
| `OTC_CARD` | 일반약 카드결제 금액 |
|
||||
| `OTC_CASH` | 일반약 현금결제 금액 |
|
||||
| `OTC_PAPER` | 일반약 외상 금액 |
|
||||
| `pAPPROVAL_NUM` | 카드 승인번호 |
|
||||
| `pMCHDATA` | 카드사 이름 |
|
||||
| `pCARDINMODE` | 카드 입력방식 (1=IC칩) |
|
||||
| `pTRDTYPE` | 거래유형 (D1=일반승인) |
|
||||
| `nCASHINMODE` | 현금영수증 모드 (1=발행, 2=카드거래 자동세팅) |
|
||||
| `nAPPROVAL_NUM` | 현금영수증 승인번호 |
|
||||
| `Appr_Gubun` | 승인구분 (1, 2, 9 등) |
|
||||
| `APPR_DATE` | 승인일시 (YYYYMMDDHHmmss) |
|
||||
| `DaeRiSunab` | 대리수납 여부 |
|
||||
| `YOHUDATE` | 요후일 |
|
||||
| 총 **54개 컬럼** | |
|
||||
|
||||
### 2. PS_main — 처방전 접수 (조제 전용)
|
||||
- **역할**: 처방전 기반 조제 접수 기록
|
||||
- **키**: `PreSerial` (처방번호 = CD_SUNAB.PRESERIAL)
|
||||
- **건수**: 하루 약 89건
|
||||
- **SALE_MAIN에는 없음** — 조제건은 SALE_MAIN을 거치지 않음
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `PreSerial` | 처방번호 (= CD_SUNAB.PRESERIAL) |
|
||||
| `Day_Serial` | 일일 접수 순번 (1~89) |
|
||||
| `Indate` | 접수일 (YYYYMMDD) |
|
||||
| `CusCode` | 환자 코드 |
|
||||
| `Paname` | 환자명 |
|
||||
| `PaNum` | 주민번호 |
|
||||
| `InsName` | 보험구분 (건강보험, 의료급여 등) |
|
||||
| `OrderName` | 의료기관명 |
|
||||
| `Drname` | 처방의사명 |
|
||||
| `PresTime` | 접수 시간 |
|
||||
| `PRICE_T` | 총금액 |
|
||||
| `PRICE_P` | 본인부담금 |
|
||||
| `PRICE_C` | 보험자부담금 |
|
||||
| `Pre_State` | 처방 상태 |
|
||||
| `InsertTime` | 입력 시간 |
|
||||
| 총 **58개 컬럼** | |
|
||||
|
||||
### 3. SALE_MAIN — OTC 직접 판매
|
||||
- **역할**: 일반의약품(OTC) 직접 판매 기록
|
||||
- **키**: `SL_NO_order` (주문번호 = CD_SUNAB.PRESERIAL)
|
||||
- **건수**: 하루 약 39건
|
||||
- **조제건은 포함되지 않음**
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `SL_NO_order` | 주문번호 (= CD_SUNAB.PRESERIAL) |
|
||||
| `SL_DT_appl` | 판매일 (YYYYMMDD) |
|
||||
| `SL_NM_custom` | 고객명 (대부분 빈값 → `[비고객]`) |
|
||||
| `SL_MY_total` | 원가 (할인 전) |
|
||||
| `SL_MY_discount` | 할인 금액 |
|
||||
| `SL_MY_sale` | 실판매가 (= total - discount) |
|
||||
| `InsertTime` | 입력 시간 |
|
||||
| `PRESERIAL` | 처방번호 (OTC는 'V' 고정, 의미 없음) |
|
||||
| 총 **30개 컬럼** | |
|
||||
|
||||
---
|
||||
|
||||
## SALE_MAIN 금액 컬럼
|
||||
## 데이터 흐름 정리
|
||||
|
||||
### 조제 (처방전 기반)
|
||||
```
|
||||
처방전 접수 → PS_main 생성 → 조제 → CD_SUNAB 수납 기록
|
||||
(ETC_CARD/ETC_CASH에 금액)
|
||||
```
|
||||
- SALE_MAIN에는 **기록되지 않음**
|
||||
- SALE_SUB에도 품목이 **들어가지 않음**
|
||||
- 환자명은 PS_main.Paname에 있음
|
||||
|
||||
### OTC 판매 (직접 판매)
|
||||
```
|
||||
POS에서 품목 선택 → SALE_MAIN + SALE_SUB 생성 → CD_SUNAB 수납 기록
|
||||
(OTC_CARD/OTC_CASH에 금액)
|
||||
```
|
||||
- PS_main에는 **기록되지 않음**
|
||||
- 고객명은 보통 빈값 (`[비고객]`)
|
||||
|
||||
### 조제 + OTC 동시 (하루 약 10건)
|
||||
```
|
||||
처방전 조제 + 일반약 동시 구매
|
||||
→ PS_main (조제 부분)
|
||||
→ SALE_MAIN + SALE_SUB (OTC 부분)
|
||||
→ CD_SUNAB 1행에 ETC + OTC 금액 모두 기록
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 조인 키 관계
|
||||
|
||||
```
|
||||
CD_SUNAB.PRESERIAL = PS_main.PreSerial (조제건)
|
||||
CD_SUNAB.PRESERIAL = SALE_MAIN.SL_NO_order (OTC건)
|
||||
```
|
||||
|
||||
**주의**: `SALE_MAIN.PRESERIAL`은 OTC에서 항상 `'V'`로, 조인키가 아님.
|
||||
실제 조인키는 `SALE_MAIN.SL_NO_order`임.
|
||||
|
||||
---
|
||||
|
||||
## 건수 관계 (2025-02-25 기준)
|
||||
|
||||
| 구분 | 건수 | 설명 |
|
||||
|------|------|------|
|
||||
| CD_SUNAB | 130 | 모든 수납 기록 |
|
||||
| PS_main | 89 | 처방전 접수 (= 조제) |
|
||||
| SALE_MAIN | 39 | OTC 직접 판매 |
|
||||
| CD_SUNAB에만 존재 | 91 | 조제건 (SALE_MAIN 없음) |
|
||||
| PS_main 매칭 | 89 | 91건 중 PS_main과 매칭 |
|
||||
| 미매칭 | 2 | PS_main 없이 수납만 존재 (미수금 수납 등 특수 케이스) |
|
||||
|
||||
### 130건 = 39 (OTC) + 89 (조제) + 2 (특수)
|
||||
|
||||
---
|
||||
|
||||
## 조제/OTC 구분 방법
|
||||
|
||||
CD_SUNAB의 ETC/OTC 금액으로 판별:
|
||||
|
||||
```python
|
||||
etc_total = ETC_CARD + ETC_CASH # 조제 금액
|
||||
otc_total = OTC_CARD + OTC_CASH # 일반약 금액
|
||||
|
||||
if etc_total > 0 and otc_total > 0:
|
||||
구분 = "조제+판매"
|
||||
elif etc_total > 0:
|
||||
구분 = "조제"
|
||||
elif otc_total > 0:
|
||||
구분 = "판매(OTC)"
|
||||
else:
|
||||
구분 = "본인부담금 없음" # 건강보험 전액 부담
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 결제수단 판별
|
||||
|
||||
```python
|
||||
card_total = ETC_CARD + OTC_CARD
|
||||
cash_total = ETC_CASH + OTC_CASH
|
||||
|
||||
# 현금영수증 판별 (nCASHINMODE=2는 카드거래 자동세팅이므로 제외)
|
||||
has_cash_receipt = (nCASHINMODE == '1' and nAPPROVAL_NUM != '')
|
||||
|
||||
if card_total > 0 and cash_total > 0:
|
||||
결제 = "카드+현금"
|
||||
elif card_total > 0:
|
||||
결제 = "카드"
|
||||
elif cash_total > 0:
|
||||
결제 = "현영" if has_cash_receipt else "현금"
|
||||
else:
|
||||
결제 = "-"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GUI 표시 색상
|
||||
|
||||
### 결제 컬럼
|
||||
- **카드**: 파란색 (#1976D2)
|
||||
- **현영**: 청록색 볼드 (#00897B) — 현금영수증 발행
|
||||
- **현금**: 주황색 (#E65100) — 현금영수증 미발행
|
||||
- **카드+현금**: 보라색 (#7B1FA2)
|
||||
- **-**: 회색 (수납 없음)
|
||||
|
||||
### 수납 컬럼
|
||||
- **✓**: 녹색 (#4CAF50)
|
||||
- **-**: 회색 (미수납)
|
||||
|
||||
### 할인 표시
|
||||
- 할인 없음: `12,000원`
|
||||
- 할인 있음: `54,000원 (-6,000)` 주황색 볼드 + 툴팁
|
||||
|
||||
---
|
||||
|
||||
## SALE_MAIN 금액 컬럼 상세
|
||||
|
||||
| 컬럼 | 설명 | 예시 |
|
||||
|------|------|------|
|
||||
@@ -40,30 +235,7 @@ SL_MY_recive ≈ SL_MY_sale / 1.1 (부가세 제외 금액 추정)
|
||||
|
||||
---
|
||||
|
||||
## CD_SUNAB 결제수단 컬럼
|
||||
|
||||
### 금액 기반 결제수단 구분
|
||||
단일 구분 컬럼이 없음. **금액이 0보다 크면 해당 결제수단 사용**.
|
||||
|
||||
| 구분 | 카드 | 현금 | 외상 |
|
||||
|------|------|------|------|
|
||||
| 조제(ETC, 전문의약품) | `ETC_CARD` | `ETC_CASH` | `ETC_PAPER` |
|
||||
| OTC(일반의약품) | `OTC_CARD` | `OTC_CASH` | `OTC_PAPER` |
|
||||
|
||||
### 결제수단 판별 로직
|
||||
```python
|
||||
card_total = ETC_CARD + OTC_CARD
|
||||
cash_total = ETC_CASH + OTC_CASH
|
||||
|
||||
if card_total > 0 and cash_total > 0:
|
||||
결제수단 = "카드+현금"
|
||||
elif card_total > 0:
|
||||
결제수단 = "카드"
|
||||
elif cash_total > 0:
|
||||
결제수단 = "현금"
|
||||
else:
|
||||
결제수단 = "-" (미수납 또는 외상)
|
||||
```
|
||||
## CD_SUNAB 카드/현금 상세 컬럼
|
||||
|
||||
### 카드 상세 정보
|
||||
| 컬럼 | 설명 | 예시 |
|
||||
@@ -79,32 +251,13 @@ else:
|
||||
### 현금 상세 정보
|
||||
| 컬럼 | 설명 | 예시 |
|
||||
|------|------|------|
|
||||
| `nCASHINMODE` | 현금영수증 입력 방식 | 1, 2 (빈값=미발행) |
|
||||
| `nAPPROVAL_NUM` | 현금영수증 승인번호 | |
|
||||
| `nCHK_GUBUN` | 현금 체크 구분 | TASA |
|
||||
| `nCASHINMODE` | 현금영수증 입력 방식 | 1=실제발행, 2=카드거래 자동세팅 |
|
||||
| `nAPPROVAL_NUM` | 현금영수증 승인번호 | 116624870 |
|
||||
| `nCHK_GUBUN` | 현금 체크 구분 | KOV, TASA |
|
||||
|
||||
---
|
||||
|
||||
## GUI 표시 방식
|
||||
|
||||
### 결제 컬럼
|
||||
- **카드**: 파란색 (#1976D2)
|
||||
- **현금**: 주황색 (#E65100)
|
||||
- **카드+현금**: 보라색 (#7B1FA2)
|
||||
- **-**: 회색 (수납 정보 없음)
|
||||
|
||||
### 수납 컬럼
|
||||
- **✓**: 녹색 (card + cash > 0)
|
||||
- **-**: 회색 (미수납)
|
||||
|
||||
### 할인 표시
|
||||
- 할인 없는 건: `12,000원` (기본)
|
||||
- 할인 있는 건: `54,000원 (-6,000)` 주황색 볼드
|
||||
- 마우스 툴팁: 원가 / 할인 / 결제 상세
|
||||
|
||||
---
|
||||
|
||||
## SQL 쿼리 (GUI에서 사용)
|
||||
## SQL 쿼리 (현재 GUI에서 사용)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
@@ -115,12 +268,16 @@ SELECT
|
||||
ISNULL(S.card_total, 0) AS card_total,
|
||||
ISNULL(S.cash_total, 0) AS cash_total,
|
||||
ISNULL(M.SL_MY_total, 0) AS total_amount,
|
||||
ISNULL(M.SL_MY_discount, 0) AS discount
|
||||
ISNULL(M.SL_MY_discount, 0) AS discount,
|
||||
S.cash_receipt_mode,
|
||||
S.cash_receipt_num
|
||||
FROM SALE_MAIN M
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1
|
||||
ISNULL(ETC_CARD, 0) + ISNULL(OTC_CARD, 0) AS card_total,
|
||||
ISNULL(ETC_CASH, 0) + ISNULL(OTC_CASH, 0) AS cash_total
|
||||
ISNULL(ETC_CASH, 0) + ISNULL(OTC_CASH, 0) AS cash_total,
|
||||
nCASHINMODE AS cash_receipt_mode,
|
||||
nAPPROVAL_NUM AS cash_receipt_num
|
||||
FROM CD_SUNAB
|
||||
WHERE PRESERIAL = M.SL_NO_order
|
||||
) S
|
||||
@@ -128,6 +285,10 @@ WHERE M.SL_DT_appl = ?
|
||||
ORDER BY M.InsertTime DESC
|
||||
```
|
||||
|
||||
**한계**: SALE_MAIN 기준이므로 OTC 판매(39건)만 표시됨.
|
||||
조제건(~89건)은 표시되지 않음. 조제건까지 보려면 CD_SUNAB을
|
||||
기본 테이블로 사용하거나 PS_main과 조인하는 쿼리 재설계 필요.
|
||||
|
||||
---
|
||||
|
||||
## 카드사 분포 (전체 데이터 기준)
|
||||
|
||||
91
docs/실행구조.md
Normal file
91
docs/실행구조.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 청춘약국 마일리지 시스템 — 실행 구조
|
||||
|
||||
## 실행해야 할 프로그램 (2개)
|
||||
|
||||
### 1. Flask 서버 (`backend/app.py`)
|
||||
```bash
|
||||
cd c:\Users\청춘약국\source\pharmacy-pos-qr-system
|
||||
python backend/app.py
|
||||
```
|
||||
- **포트**: 7001 (0.0.0.0)
|
||||
- **외부 도메인**: `mile.0bin.in` (→ 내부 7001 포트로 프록시)
|
||||
- **역할**: 웹 서비스 전체 담당
|
||||
|
||||
#### 제공하는 페이지/API
|
||||
| 경로 | 설명 |
|
||||
|------|------|
|
||||
| `/` | 메인 페이지 |
|
||||
| `/signup` | 회원가입 |
|
||||
| `/claim` | QR 적립 (폰번호 방식) |
|
||||
| `/claim/kakao/start` | QR 적립 (카카오 로그인) |
|
||||
| `/my-page` | 마이페이지 |
|
||||
| `/kiosk` | **키오스크 대기 화면** (약국 내 태블릿) |
|
||||
| `/admin` | 관리자 페이지 |
|
||||
| `/admin/transaction/<id>` | 거래 상세 |
|
||||
| `/admin/user/<id>` | 회원 상세 |
|
||||
| `/admin/search/user` | 회원 검색 |
|
||||
| `/admin/search/product` | 상품 검색 |
|
||||
| `/api/kiosk/trigger` | 키오스크 QR 트리거 (POST) |
|
||||
| `/api/kiosk/current` | 키오스크 현재 상태 |
|
||||
| `/api/kiosk/claim` | 키오스크 적립 처리 (POST) |
|
||||
|
||||
#### 사용하는 DB
|
||||
- **SQLite** (`backend/db/mileage.db`) — 회원, 적립, QR 토큰
|
||||
- **MSSQL** (`192.168.0.4\PM2014`, DB: `PM_PRES`) — POS 판매 데이터 (읽기 전용)
|
||||
|
||||
---
|
||||
|
||||
### 2. Qt POS GUI (`backend/gui/pos_sales_gui.py`)
|
||||
```bash
|
||||
cd c:\Users\청춘약국\source\pharmacy-pos-qr-system
|
||||
python backend/gui/pos_sales_gui.py
|
||||
```
|
||||
- **역할**: POS 판매 내역 조회 + QR 라벨 발행
|
||||
- **PyQt5 기반** 데스크톱 앱
|
||||
- Flask 서버와 **독립적으로 실행** (별도 프로세스)
|
||||
|
||||
#### 주요 기능
|
||||
- 일자별 판매 내역 조회 (SALE_MAIN + CD_SUNAB)
|
||||
- 결제수단 표시 (카드/현금/현영)
|
||||
- 할인 표시
|
||||
- QR 라벨 프린터 출력 (Zebra / POS 프린터)
|
||||
- 적립자 클릭 → 회원 적립 내역 팝업
|
||||
|
||||
#### 사용하는 DB
|
||||
- **MSSQL** — SALE_MAIN, SALE_SUB, CD_SUNAB 조회
|
||||
- **SQLite** — claim_tokens, users 조회 (적립 정보)
|
||||
|
||||
---
|
||||
|
||||
## 실행 순서
|
||||
|
||||
```
|
||||
1. Flask 서버 먼저 실행 (키오스크, 웹 서비스 제공)
|
||||
2. Qt POS GUI 실행 (판매 내역 조회, QR 발행)
|
||||
```
|
||||
|
||||
순서는 상관없으나, Flask가 먼저 떠 있어야 키오스크(`mile.0bin.in/kiosk`)와
|
||||
웹 서비스(`mile.0bin.in`)가 접속 가능.
|
||||
|
||||
---
|
||||
|
||||
## 프로세스 확인
|
||||
|
||||
```bash
|
||||
# 실행 중인 Python 프로세스 확인
|
||||
tasklist /FI "IMAGENAME eq python.exe"
|
||||
|
||||
# 정상 상태: Python 프로세스 3개
|
||||
# - Flask 서버 (메인)
|
||||
# - Flask 서버 (debug reloader 워커)
|
||||
# - Qt POS GUI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 주의사항
|
||||
|
||||
- `taskkill /F /IM python.exe` 사용 시 **Flask + GUI 모두 종료됨**
|
||||
- GUI만 재시작하려면 해당 PID만 종료할 것
|
||||
- Flask 서버는 `debug=True`로 실행되어 코드 변경 시 자동 리로드
|
||||
- Python 경로: `C:\Users\청춘약국\AppData\Local\Programs\Python\Python312\python.exe`
|
||||
Reference in New Issue
Block a user