Compare commits

...

7 Commits

Author SHA1 Message Date
root
9836d2bdab fix: V2 비급여 매출금액 계산 수정 - EXE_PRICE 사용
비급여(MPRE_TYPE='9') 처방의 매출금액:
- V1: PRICE_T에 비급여 총액이 들어감
- V2: TOT_PRICE는 조제료만, EXE_PRICE에 비급여 총액

수정:
- PRICE_T = CASE WHEN MPRE_TYPE='9' THEN EXE_PRICE ELSE TOT_PRICE END

검증 (2026년 3월):
- V1 비급여 매출: 3,804,250
- V2 비급여 매출: 3,804,250
- 차이: 0 
2026-04-01 15:34:56 +00:00
root
fc0f6832c3 fix: V1 쿼리에 PS_Main_Sub 조인 추가 - 선별급여/상한제 합산
V1 PharmIT3000의 PRICE_N은 선별급여/상한제초과 금액 미포함 버그 있음.
PS_Main_Sub.SE_PRICE_P를 합산해야 진짜 수납액.

변경:
- PS_Main_Sub LEFT JOIN 추가
- PRICE_N = m.PRICE_N + ISNULL(s.SE_PRICE_P, 0)

결과:
- V1 수납액 합계: 64,680,430
- V2 수납액 합계: 64,680,430
- 차이: 0 (완벽 일치)
2026-04-01 15:29:02 +00:00
root
bc0c282eaf docs: V1 PS_Main_Sub 선별급여/상한제 스키마 분석 추가
V1 PharmIT3000 구조 분석:
- PS_Main: 처방헤더 (PRICE_N만, 합산 안됨)
- PS_Main_Sub: 처방 부가정보 (SE_PRICE_P_80 등 선별급여 컬럼 있음!)
- CD_SUNAB: 수납 (ETC_CARD에 합산 결과 저장)

V1 버그 원인:
- 수납 화면에서는 PS_Main + PS_Main_Sub JOIN해서 계산
- CD_SUNAB.ETC_CARD에 정확히 저장
- 하지만 PS_Main.PRICE_N은 업데이트 안 함

SE_PRICE 컬럼 이력:
- PS_Main_Sub 테이블: 2010년 초기 스키마에 생성
- SE_PRICE_P_* 컬럼: 선별급여 제도 시행 후 추가
- 실제 데이터: 2026년부터 6건 존재

V2 PMPLUS20 개선:
- REAL_PRICE에 모든 항목 합산해서 저장
- 수납테이블 RECP_AMT와 일치 보장
2026-04-01 15:26:58 +00:00
root
75559a78f9 docs: PRICE_N vs REAL_PRICE 매핑 상세 분석 추가
- 일반 환자: REAL_PRICE = PRICE_N 정확히 일치 확인
- 특수 보험 환자 (차상위/선별급여):
  - 지성윤(차상위2): 상한제초과분(EXP_UNDER_EXE_PRICE) 포함
  - 임해자(선별급여): 선별급여80%(SE_PRICE_P_80) 포함
- v1 PRICE_N 버그: 상한제초과/선별급여분 미포함
- v2 REAL_PRICE = 수납테이블(TBSIR000_01)의 RECP_AMT와 일치
- 결론: REAL_PRICE 매핑 유지 (v1보다 정확한 실제 결제금액)
2026-04-01 15:07:35 +00:00
root
d6a1484b54 docs: 보험구분 매핑 문서 업데이트 - 전체 보험종류 포함
- 3-2절: MPRE_TYPE + MPRE_TYPE_GUBUN 조합 매핑 추가
  - 건강보험(0), 의료급여1종(1), 산재(2), 의료급여2종(3)
  - 차상위1(7←4+C), 보훈(E←4+E), 차상위2(F←4+F), 비급여(9)
- 4절: v2 쿼리에 정확한 CASE문 반영
- 5절: 2026년 3월 전체 검증 결과로 업데이트 (V1=V2 일치 확인)
- 6절: 작업 이력으로 변경, 완료된 항목 체크
2026-04-01 14:57:56 +00:00
root
defebd5554 fix: 보험구분 매핑 수정 - 보훈/차상위1/차상위2 정확한 변환
V2 PMPLUS20에서 MPRE_TYPE='4'는 특수보험 그룹:
- MPRE_TYPE_GUBUN='E' → 보훈 (V1: 'E')
- MPRE_TYPE_GUBUN='F' → 차상위2 (V1: 'F')
- MPRE_TYPE_GUBUN='C' → 차상위1 (V1: '7')

기존 코드는 MPRE_TYPE_GUBUN='F'만 체크해서 차상위2만 인식하고
보훈과 차상위1이 누락되는 문제가 있었음
2026-04-01 14:53:33 +00:00
root
8b4e8f7a0a feat: PMPLUS20(v2) 테이블 매핑 완료 및 쿼리 수정
- config.py: PMPLUS20 서버(192.168.0.201\PMPLUS20) 및 SA 비밀번호 수정
- v2_pmplus20.py: PS_MAIN→TBSID040_03, CD_SUNAB→TBSIR000_01 매핑 적용
  - 컬럼 매핑: PRICE_T→TOT_PRICE, PRICE_C→INS_PRICE, PRICE_P→EXE_PRICE 등
  - Drug_T4 = NON_DRUG_PRICE + EXP_EXE_PRICE
  - Holiday = HD_ADD + PRES_TIME_GUBUN 조합
  - PreGubun = MPRE_TYPE (차상위 F 별도 처리)
  - PRES_GUBUN='E' 재고보정 레코드 제외
- PMPLUS20_MIGRATION_GUIDE.md: 전체 매핑 가이드 문서 추가
- app.py: 포트 5060→5050 변경

20260324 기준 검증: 건수/금액/보험별/결제별 일치 확인
PRICE_N(수납) 2건 차이(23,420원)는 비급여 수납 처리 개선에 의한 정상 차이

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:45:31 +00:00
9 changed files with 400 additions and 33 deletions

328
PMPLUS20_MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,328 @@
# PMPLUS20 테이블 매핑 가이드 (pharmacy-stats-api v2)
## 1. DB 연결 정보
| 구분 | 서버 | DB | SA 비밀번호 |
|------|------|-----|------------|
| v1 PharmIT3000 | `192.168.0.201\PM2014` | PM_PRES | `tmddls214!%(` |
| v2 PMPLUS20 | `192.168.0.201\PMPLUS20` | PM_MAIN | `newpro2020!@` |
> **IP는 반드시 192.168.0.201 고정** (두 인스턴스 SA 비밀번호 다름)
---
## 2. 테이블 매핑 (확정)
### 처방 조제 통계 (pharmacy-stats-api용)
| 역할 | v1 (PharmIT3000) | v2 (PMPLUS20) | 검증 |
|------|-----------------|---------------|------|
| 처방 헤더 | `PM_PRES..PS_MAIN` | `PM_MAIN..TBSID040_03` | ✅ 확정 |
| 수납/결제 | `PM_PRES..CD_SUNAB` | `PM_MAIN..TBSIR000_01` | ✅ 확정 |
### JOIN 관계
```sql
-- v1
FROM PM_PRES..PS_MAIN m
LEFT JOIN PM_PRES..CD_SUNAB n ON n.PRESERIAL = m.PreSerial
-- v2
FROM PM_MAIN..TBSID040_03 m
LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.DRUG_SEQ = m.DRUG_SEQ
```
### 참고: OTC 판매 매핑 (person-lookup-web-local용, 별도)
| 역할 | v1 | v2 |
|------|-----|-----|
| OTC 판매 헤더 | SALE_MAIN | TBSIM040_28 |
| OTC 판매 상세 | SALE_SUB | TBSIM040_29 |
| OTC 결제 수납 | CD_SUNAB | TBSIR000_01 |
---
## 3. 컬럼 매핑 (확정)
### 3-1. 처방 헤더 (PS_MAIN → TBSID040_03)
| v1 컬럼 | v2 컬럼 | 역할 | 검증 금액 (20260324) | 상태 |
|---------|---------|------|---------------------|------|
| `PreSerial` | `DRUG_SEQ` | 처방 일련번호 | 20260324000004 = 동일 | ✅ |
| `INDATE` | `SUNAB_DT` | 조제일자 (YYYYMMDD) | 동일 | ✅ |
| `PRICE_T` | `TOT_PRICE` | 보험 약제비 총액 | 9,476,000 = 동일 | ✅ |
| `PRICE_C` | `INS_PRICE` | 청구금액 | 7,068,800 = 동일 | ✅ |
| `PRICE_P` | `EXE_PRICE` | 본인부담금 | 2,407,200 = 동일 | ✅ |
| `PRICE_N` | `REAL_PRICE` | 수납금액 | 2,838,420 (차이 23,420원 — 아래 참고) | ✅ |
| `S_Prep` | `INS_PREP_PRICE` | 조제료 | 2,277,850 = 동일 | ✅ |
| `Drug_T4` | `NON_DRUG_PRICE + EXP_EXE_PRICE` | 비급여 약제비 | 292,140+120,420=412,560 ✅ | ✅ |
| `PaNum` | `PAT_JUMIN_NO` | 주민번호 (연령 계산) | 동일 | ✅ |
| `PaName` | `PAT_NM` | 환자명 | 동일 | ✅ |
| `OrderName` | `YOYANG_NM` | 처방 병원명 | 동일 | ✅ |
### 3-2. 보험구분 (PreGubun → MPRE_TYPE + MPRE_TYPE_GUBUN)
v2에서 `MPRE_TYPE='4'`는 특수보험 그룹이며, `MPRE_TYPE_GUBUN`으로 세부 구분함.
| v1 `PreGubun` | v2 `MPRE_TYPE` | v2 `MPRE_TYPE_GUBUN` | 의미 | 건수 (202603) |
|---------------|---------------|---------------------|------|----------------|
| `'0'` | `'0'` | `'0'` | 건강보험 | 3,325건 ✅ |
| `'1'` | `'1'` | `'0'` | 의료급여1종 | 259건 ✅ |
| `'2'` | `'2'` | `'0'` | 산재 | 17건 ✅ |
| `'3'` | `'3'` | `'0'` | 의료급여2종 | 6건 ✅ |
| `'7'` | `'4'` | `'C'` | **차상위1** | 7건 ✅ |
| `'E'` | `'4'` | `'E'` | **보훈** | 32건 ✅ |
| `'F'` | `'4'` | `'F'` | **차상위2** | 11건 ✅ |
| `'9'` | `'9'` | `'0'` | 비급여 | 34건 ✅ |
**v2에서 PreGubun 변환 SQL**:
```sql
CASE
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'E' THEN 'E' -- 보훈
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'F' THEN 'F' -- 차상위2
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'C' THEN '7' -- 차상위1
ELSE ISNULL(m.MPRE_TYPE, '0')
END AS PreGubun
```
> **주의**: `MPRE_TYPE`이 기본 보험구분. `PRES_GUBUN`은 다른 용도 (A/E 값)
> **v2 전용 필터**: `PRES_GUBUN = 'E'`인 "재고보정" 레코드는 제외해야 함
> (DRUG_SEQ='20260324099999' 형태, v1에는 없음)
### 3-3. 시간가산 (Holiday → HD_ADD + PRES_TIME_GUBUN)
v1의 `Holiday`는 단일 컬럼, v2는 두 컬럼 조합:
| v2 `HD_ADD` | v2 `PRES_TIME_GUBUN` | v1 `Holiday` | 의미 |
|------------|---------------------|-------------|------|
| `'1'` | `'0'` | `'1'` | 일반 | ✅ |
| `'2'` | `'0'` | `'2'` | 공휴일 가산 | ✅ |
| `'1'` | `'1'` | `'3'` | 시간외 가산 | ✅ |
| `'2'` | `'1'` | `'4'` | 공휴+시간외 | 추정 (데이터 미확인) |
**v2에서 Holiday 계산 공식**:
```python
# PMPLUS20 → PharmIT3000 Holiday 변환
if hd_add == '2' and time_gubun == '1':
holiday = '4' # 공휴+시간외
elif hd_add == '2':
holiday = '2' # 공휴
elif time_gubun == '1':
holiday = '3' # 시간외
else:
holiday = '1' # 일반
```
### 3-4. 수납/결제 (CD_SUNAB → TBSIR000_01)
| v1 컬럼 | v2 컬럼 | 역할 | 상태 |
|---------|---------|------|------|
| `CD_SUNAB.PRESERIAL` | `TBSIR000_01.DRUG_SEQ` | JOIN 키 | ✅ |
| `CD_SUNAB.Appr_Gubun` | `TBSIR000_01.APPR_GUBUN` | 결제수단 구분 | ✅ 값 동일 |
| `CD_SUNAB.nAPPROVAL_NUM` | `TBSIR000_01.CARD_ADM_NO` | 승인번호 | ✅ |
결제 구분 값 분포 (20260324):
- `'9'`: 179건 (카드)
- `'A'`: 24건
- `'0'`: 5건
- `'2'`: 3건 (현금)
---
## 4. v2 쿼리 (변환 완료)
```sql
SELECT
m.DRUG_SEQ AS PreSerial,
-- 보험구분 변환: MPRE_TYPE + MPRE_TYPE_GUBUN → PreGubun
CASE
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'E' THEN 'E' -- 보훈
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'F' THEN 'F' -- 차상위2
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'C' THEN '7' -- 차상위1
ELSE ISNULL(m.MPRE_TYPE, '0')
END AS PreGubun,
m.PAT_JUMIN_NO AS PaNum,
m.YOYANG_NM AS OrderName,
-- Holiday 변환: HD_ADD + PRES_TIME_GUBUN → Holiday
CASE
WHEN m.HD_ADD = '2' AND m.PRES_TIME_GUBUN = '1' THEN '4'
WHEN m.HD_ADD = '2' THEN '2'
WHEN m.PRES_TIME_GUBUN = '1' THEN '3'
ELSE '1'
END AS Holiday,
m.TOT_PRICE AS PRICE_T,
(ISNULL(m.NON_DRUG_PRICE, 0) + ISNULL(m.EXP_EXE_PRICE, 0)) AS Drug_T4,
m.INS_PREP_PRICE AS S_Prep,
m.INS_PRICE AS PRICE_C,
m.EXE_PRICE AS PRICE_P,
m.REAL_PRICE AS PRICE_N,
ISNULL(n.APPR_GUBUN, '') AS Appr_Gubun,
ISNULL(n.CARD_ADM_NO, '') AS nAPPROVAL_NUM
FROM PM_MAIN..TBSID040_03 m
LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.DRUG_SEQ = m.DRUG_SEQ
WHERE m.SUNAB_DT BETWEEN ? AND ?
AND m.PRES_GUBUN != 'E' -- 재고보정 레코드 제외
```
---
## 5. 데이터 검증 결과 (20260324)
### 건수
| 항목 | v1 | v2 | 비고 |
|------|-----|-----|------|
| 전체 건수 | 211건 | 212건 | v2에 "재고보정" 1건 추가 (PRES_GUBUN='E' 필터로 해결) |
| 필터 후 | 211건 | 211건 | ✅ 일치 |
### 금액 합계
| 항목 | v1 | v2 | 일치 |
|------|-----|-----|------|
| PRICE_T (보험약제비) | 9,476,000 | 9,476,000 | ✅ |
| PRICE_C (청구금액) | 7,068,800 | 7,068,800 | ✅ |
| PRICE_P (본인부담금) | 2,407,200 | 2,407,200 | ✅ |
| S_Prep (조제료) | 2,277,850 | 2,277,850 | ✅ |
| Drug_T4 (비급여약제비) | 412,560 | 412,560 | ✅ (NON_DRUG+EXP_EXE) |
| PRICE_N (수납금액) | 2,815,000 | 2,838,420 | ⚠️ 차이 23,420원 (아래 설명) |
### PRICE_N vs REAL_PRICE 매핑 상세 분석
#### 결론: `REAL_PRICE = 진짜 수납금액` ✅
PMPLUS20의 `REAL_PRICE`는 수납테이블(TBSIR000_01)의 `RECP_AMT`, `SUNAB_PRICE`와 정확히 일치함.
#### 검증 케이스 (20260324)
| 환자 | 보험구분 | v1 PRICE_N | v2 REAL_PRICE | v2 수납(RECP_AMT) | 설명 |
|------|---------|-----------|--------------|-----------------|------|
| 박명순 | 건강보험+비급여 | 89,600 | 89,600 | 89,600 | 일반 케이스 ✅ |
| 김상훈 | 건강보험+비급여 | 70,900 | 70,900 | 70,900 | 일반 케이스 ✅ |
| 지성윤 | 차상위2(F) | 500 | 11,920 | 11,920 | 특수 케이스 ⚠️ |
| 임해자 | 건강보험+선별급여 | 19,700 | 31,700 | 31,700 | 특수 케이스 ⚠️ |
#### 일반 환자 (건강보험 + 비급여)
```
REAL_PRICE = EXE_PRICE(본인부담) + NON_DRUG_PRICE(비급여약) + EXP_EXE_PRICE(비급여조제)
= PRICE_N (정확히 일치)
```
#### 특수 보험 환자 (차상위/선별급여)
**지성윤 (차상위2)**:
```
EXE_PRICE = 500 (기본 본인부담)
EXP_UNDER_EXE_PRICE = 11,420 (상한제초과 본인부담)
REAL_PRICE = 500 + 11,420 = 11,920 (진짜 수납액)
v1 PRICE_N = 500 ← 상한제초과분 누락!
```
**임해자 (건강보험 + 선별급여 80%)**:
```
EXE_PRICE = 19,700 (기본 본인부담)
SE_PRICE_P_80 = 12,000 (선별급여 80% 본인부담)
REAL_PRICE = 19,700 + 12,000 = 31,700 (진짜 수납액)
v1 PRICE_N = 19,700 ← 선별급여분 누락!
```
#### v1 스키마 분석: 선별급여/상한제 데이터는 어디에?
**V1 PharmIT3000에도 데이터는 있다!** `PS_Main_Sub` 테이블에 저장됨:
```sql
-- V1 PharmIT3000에서 선별급여/상한제 확인
SELECT
m.PreSerial, m.PRICE_N,
s.SE_PRICE_P_80, -- 선별급여 80% 본인부담
s.SE_PRICE_P, -- 선별급여 본인부담 합계
c.ETC_CARD -- 실제 수납액
FROM PS_Main m
JOIN PS_Main_Sub s ON s.PreSerial = m.PreSerial
JOIN CD_SUNAB c ON c.PRESERIAL = m.PreSerial
WHERE s.SE_PRICE_P > 0
```
**V1 테이블 구조:**
| 테이블 | 역할 | 선별급여/상한제 |
|--------|-----|-------------------|
| `PS_Main` | 처방 헤더 | ❌ `PRICE_N`만 (합산 안됨) |
| `PS_Main_Sub` | 처방 부가정보 | ✅ `SE_PRICE_P_80`, `SE_PRICE_P` 등 |
| `CD_SUNAB` | 수납 | ✅ `ETC_CARD`에 합산 결과 |
**검증 케이스 (V1):**
| 환자 | PS_Main.PRICE_N | PS_Main_Sub.SE_PRICE_P_80 | CD_SUNAB.ETC_CARD |
|------|----------------|--------------------------|------------------|
| 지성윤(차상위2) | 500 | 11,420 | 11,920 ✅ |
| 임해자(선별급여) | 19,700 | 12,000 | 31,700 ✅ |
**문제점**: `PS_Main.PRICE_N``PS_Main_Sub`의 선별급여/상한제 금액을 **합산하지 않음**.
#### 선별급여 컨럼 추가 이력
- `PS_Main_Sub` 테이블: **2010년 초기 스키마**에 생성
- `SE_PRICE_P_*` 커럼들: **나중에 추가됨** (선별급여 제도 시행 후)
- 실제 데이터: 2026년부터 6건 존재 (최근 사용 시작)
#### V1 수납 프로세스 (추정)
1. 수납 화면에서 `PS_Main` + `PS_Main_Sub` JOIN해서 총액 계산
2. 계산된 금액을 `CD_SUNAB.ETC_CARD`에 저장
3. **하지만** `PS_Main.PRICE_N`은 업데이트 안 함 (버그)
#### V2 PMPLUS20 개선 사항
PMPLUS20은 이 문제를 해결:
- `TBSID040_03.REAL_PRICE`**모든 항목 합산해서 저장**
- `EXP_UNDER_EXE_PRICE`, `SE_PRICE_P_80` 등 별도 컨럼도 유지
- 수납테이블(TBSIR000_01)의 `RECP_AMT`와 일치 보장
**결론**: `REAL_PRICE` 매핑 유지가 맞음. V1의 `PRICE_N` 버그를 V2에서 수정한 것.
### 보험구분별 (2026년 3월 전체 검증)
| 보험구분 | v1 건수 | v2 건수 | 일치 |
|---------|--------|--------|------|
| 0 (건강보험) | 3,359 | 3,359 | ✅ |
| 1 (의료급여1종) | 271 | 271 | ✅ |
| 2 (산재) | 17 | 17 | ✅ |
| 3 (의료급여2종) | 6 | 6 | ✅ |
| 7 (차상위1) | 8 | 8 | ✅ |
| 9 (비급여) | 35 | 35 | ✅ |
| E (보훈) | 32 | 32 | ✅ |
| F (차상위2) | 11 | 11 | ✅ |
### 결제수단별
| 구분 | v1 건수 | v2 건수 | 일치 |
|------|--------|--------|------|
| 9 (카드) | 179 | 179 | ✅ |
| A | 24 | 24 | ✅ |
| 0 | 5 | 5 | ✅ |
| 2 (현금) | 3 | 3 | ✅ |
---
## 6. 작업 이력
- [x] 테이블 매핑 확정 (PS_MAIN → TBSID040_03)
- [x] 컬럼 매핑 확정 (13개 컬럼 전체)
- [x] 수납 테이블 매핑 확정 (CD_SUNAB → TBSIR000_01)
- [x] 변환 쿼리 작성
- [x] `queries/v2_pmplus20.py` 코드 수정 (2026-04-01)
- [x] 보험구분 매핑 완료 - 보훈/차상위1/차상위2 정확히 변환 (2026-04-01)
- [x] 2026년 3월 전체 기간 보험구분별 건수 검증 완료 (V1=V2 일치)
- [ ] `/api/compare`로 UI 수치 검증
- [ ] 시간별/결제별/병원별 세부 수치 검증
---
## 7. 참고: PMPLUS20 주요 테이블 구조 (210개 중 데이터 있는 것)
| 테이블 | 행수 | 추정 역할 |
|--------|------|----------|
| TBSWH040_02 | 6,533,993 | 입고 이력 상세 |
| TBSID040_05 | 1,543,564 | 조제 상세 (처방전 약품) |
| TBSID040_04 | 1,542,882 | 조제 상세 (원본) |
| TBSIR000_01 | 426,751 | **수납/결제 정보** |
| TBSID040_03 | 394,802 | **처방 헤더 (= PS_MAIN)** |
| TBSIM040_01 | 188,852 | 약품 마스터 (= CD_GOODS) |
| TBSIM040_29 | 49,161 | OTC 판매 상세 (= SALE_SUB) |
| TBSIM040_28 | 28,813 | OTC 판매 헤더 (= SALE_MAIN) |

Binary file not shown.

2
app.py
View File

@@ -305,4 +305,4 @@ if __name__ == '__main__':
print(" /v2/api/stats/... - PMPLUS20 (동일 구조)")
print("")
print(" /api/compare - v1 vs v2 비교")
app.run(host='0.0.0.0', port=5060, debug=True)
app.run(host='0.0.0.0', port=5050, debug=True)

View File

@@ -4,7 +4,7 @@ import os
# PharmIT3000 (v1)
PHARMIT3000_CONFIG = {
'server': os.getenv('PHARMIT_SERVER', '192.168.0.4\\PM2014'),
'server': os.getenv('PHARMIT_SERVER', '192.168.0.201\\PM2014'),
'database': 'PM_PRES',
'username': os.getenv('PHARMIT_USER', 'sa'),
'password': os.getenv('PHARMIT_PASS', 'tmddls214!%('),
@@ -12,8 +12,8 @@ PHARMIT3000_CONFIG = {
# PMPLUS20 (v2)
PMPLUS20_CONFIG = {
'server': os.getenv('PMPLUS_SERVER', '192.168.0.4\\PM2014'),
'server': os.getenv('PMPLUS_SERVER', '192.168.0.201\\PMPLUS20'),
'database': 'PM_MAIN',
'username': os.getenv('PMPLUS_USER', 'sa'),
'password': os.getenv('PMPLUS_PASS', 'tmddls214!%('),
'password': os.getenv('PMPLUS_PASS', 'newpro2020!@'),
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,10 +4,15 @@ PharmIT3000 통계 쿼리 (v1)
테이블:
- PM_PRES.PS_MAIN: 처방 헤더
- PM_PRES.PS_Main_Sub: 처방 부가정보 (선별급여/상한제 등)
- PM_PRES.PS_SUB_PHARM: 처방 상세
- PM_PRES.CD_SUNAB: 수납 정보
- PM_DRUG.CD_GOODS: 약품 마스터
주의:
- PRICE_N은 선별급여/상한제초과 미포함 (버그)
- PS_Main_Sub.SE_PRICE_P를 합산해야 진짜 수납액
QT-POS sales_stats_dialog.py 기반
"""
import pyodbc
@@ -62,10 +67,12 @@ def get_sales_stats(date_from: str, date_to: str) -> dict:
m.S_Prep,
m.PRICE_C,
m.PRICE_P,
m.PRICE_N,
-- 진짜 수납액: PRICE_N + 선별급여/상한제초과 (PS_Main_Sub)
(m.PRICE_N + ISNULL(s.SE_PRICE_P, 0)) AS PRICE_N,
ISNULL(n.Appr_Gubun, '') AS Appr_Gubun,
ISNULL(n.nAPPROVAL_NUM, '') AS nAPPROVAL_NUM
FROM PM_PRES..PS_MAIN m
LEFT JOIN PM_PRES..PS_Main_Sub s ON s.PreSerial = m.PreSerial
LEFT JOIN PM_PRES..CD_SUNAB n ON n.PRESERIAL = m.PreSerial
WHERE m.INDATE BETWEEN ? AND ?
"""

View File

@@ -3,14 +3,33 @@
PMPLUS20 통계 쿼리 (v2)
테이블 매핑 (PharmIT3000 → PMPLUS20):
- PM_PRES.PS_MAIN → PM_MAIN.TBSIM040_28
- PM_PRES.PS_SUB_PHARM → PM_MAIN.TBSIM040_29
- PM_PRES.CD_SUNAB → PM_MAIN.TBSIR000_01
- PM_DRUG.CD_GOODS → PM_MAIN.TBSIM040_01
- PM_PRES.PS_MAIN → PM_MAIN.TBSID040_03 (처방 헤더)
- PM_PRES.CD_SUNAB → PM_MAIN.TBSIR000_01 (수납/결제, DRUG_SEQ로 JOIN)
컬럼 매핑:
- PreSerial → DRUG_SEQ
- INDATE → SUNAB_DT
- PRICE_T → TOT_PRICE, PRICE_C → INS_PRICE, PRICE_P → EXE_PRICE, PRICE_N → REAL_PRICE
- S_Prep → INS_PREP_PRICE
- Drug_T4 → NON_DRUG_PRICE + EXP_EXE_PRICE
- PreGubun → MPRE_TYPE + MPRE_TYPE_GUBUN 조합 (아래 매핑 참고)
- Holiday → HD_ADD + PRES_TIME_GUBUN 조합
- Appr_Gubun → APPR_GUBUN (값 동일)
- nAPPROVAL_NUM → CARD_ADM_NO
보험구분 매핑 (V1 PreGubun ↔ V2):
- '0' ← MPRE_TYPE='0' (건강보험)
- '1' ← MPRE_TYPE='1' (의료급여1종)
- '2' ← MPRE_TYPE='2' (산재)
- '3' ← MPRE_TYPE='3' (의료급여2종)
- '7' ← MPRE_TYPE='4' + MPRE_TYPE_GUBUN='C' (차상위1)
- 'E' ← MPRE_TYPE='4' + MPRE_TYPE_GUBUN='E' (보훈)
- 'F' ← MPRE_TYPE='4' + MPRE_TYPE_GUBUN='F' (차상위2)
- '9' ← MPRE_TYPE='9' (비급여)
주의:
- DrugCode vs Drug_Code 대소문자 차이
- TBSIR000_01.RECP_DT는 datetime (변환 필요)
- PRES_GUBUN='E' (재고보정) 레코드 제외 필요
- MPRE_TYPE='4'는 특수보험 그룹, MPRE_TYPE_GUBUN으로 세부 구분
"""
import pyodbc
from datetime import date
@@ -53,28 +72,41 @@ def get_sales_stats(date_from: str, date_to: str) -> dict:
dict: {total, by_gubun, by_age, by_time, by_pay, by_hosp}
Note:
컬럼명/테이블명은 실제 PMPLUS20 스키마 확인 후 수정 필요
TBSID040_03(처방헤더) + TBSIR000_01(수납) 매핑 확정 (2026-04-01)
"""
# TODO: 실제 PMPLUS20 스키마 확인 후 쿼리 수정
# 현재는 PharmIT3000과 동일한 컬럼명 가정
sql = """
SELECT
m.PreSerial,
m.PreGubun,
m.PaNum,
m.OrderName,
m.Holiday,
m.PRICE_T,
m.Drug_T4,
m.S_Prep,
m.PRICE_C,
m.PRICE_P,
m.PRICE_N,
ISNULL(n.Appr_Gubun, '') AS Appr_Gubun,
ISNULL(n.nAPPROVAL_NUM, '') AS nAPPROVAL_NUM
FROM PM_MAIN..TBSIM040_28 m
LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.PRESERIAL = m.PreSerial
WHERE m.INDATE BETWEEN ? AND ?
m.DRUG_SEQ AS PreSerial,
CASE
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'E' THEN 'E' -- 보훈
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'F' THEN 'F' -- 차상위2
WHEN m.MPRE_TYPE = '4' AND m.MPRE_TYPE_GUBUN = 'C' THEN '7' -- 차상위1
ELSE ISNULL(m.MPRE_TYPE, '0')
END AS PreGubun,
m.PAT_JUMIN_NO AS PaNum,
m.YOYANG_NM AS OrderName,
CASE
WHEN m.HD_ADD = '2' AND m.PRES_TIME_GUBUN = '1' THEN '4'
WHEN m.HD_ADD = '2' THEN '2'
WHEN m.PRES_TIME_GUBUN = '1' THEN '3'
ELSE '1'
END AS Holiday,
-- 비급여(MPRE_TYPE='9')는 TOT_PRICE가 아닌 EXE_PRICE가 매출금액
CASE
WHEN m.MPRE_TYPE = '9' THEN m.EXE_PRICE
ELSE m.TOT_PRICE
END AS PRICE_T,
(ISNULL(m.NON_DRUG_PRICE, 0) + ISNULL(m.EXP_EXE_PRICE, 0)) AS Drug_T4,
m.INS_PREP_PRICE AS S_Prep,
m.INS_PRICE AS PRICE_C,
m.EXE_PRICE AS PRICE_P,
m.REAL_PRICE AS PRICE_N,
ISNULL(n.APPR_GUBUN, '') AS Appr_Gubun,
ISNULL(n.CARD_ADM_NO, '') AS nAPPROVAL_NUM
FROM PM_MAIN..TBSID040_03 m
LEFT JOIN PM_MAIN..TBSIR000_01 n ON n.DRUG_SEQ = m.DRUG_SEQ
WHERE m.SUNAB_DT BETWEEN ? AND ?
AND m.PRES_GUBUN != 'E'
"""
try:
@@ -82,7 +114,7 @@ def get_sales_stats(date_from: str, date_to: str) -> dict:
except Exception as e:
return {
'error': str(e),
'note': 'PMPLUS20 스키마 확인 필요. 컬럼명/테이블명이 다를 수 있음.'
'note': 'PMPLUS20 쿼리 오류. TBSID040_03/TBSIR000_01 테이블 확인 필요.'
}
ref_date = date(