pharmacy-pos-qr-system/docs/DB구조_제품입고판매마진.md
thug0bin aed0c314b7 docs: DB 구조 분석 - 제품/입고/판매/마진 흐름 문서화
- 핵심 테이블 구조 (CD_GOODS, SALE_SUB, WH_sub 등)
- 바코드 매핑 구조 (대표바코드, 단위바코드)
- 마진 계산 로직 분석
- 마진 0 문제 원인 파악: 입고 없이 판매 시 INPRICE=0
2026-03-13 14:57:54 +09:00

411 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 약국 POS DB 구조 - 제품/입고/판매/마진
> **분석일**: 2026-03-13
> **DB**: PM_DRUG, PM_PRES (MSSQL, 192.168.0.4\PM2014)
> **목적**: 입고 기록 없이 판매 시 마진이 0으로 나오는 문제 원인 파악
---
## 1. 개요
약국 POS 시스템(PharmIT3000)은 크게 3개의 DB를 사용합니다:
| DB명 | 용도 | 주요 테이블 |
|------|------|------------|
| **PM_DRUG** | 약품 마스터, 재고, 입고 | CD_GOODS, IM_total, WH_main/sub |
| **PM_PRES** | 판매, 처방 | SALE_MAIN/SUB, PS_main/sub_pharm |
| **PM_BASE** | 고객, 도매상 정보 | CD_PERSON, CD_custom |
---
## 2. 핵심 테이블
### 2.1 제품 마스터 - PM_DRUG.CD_GOODS
```sql
-- 핵심 컬럼
DrugCode NVARCHAR(20) -- 제품코드 (PK)
GoodsName NVARCHAR(80) -- 제품명
BARCODE NVARCHAR(20) -- 대표 바코드
Price DECIMAL -- 입고가 (매입가) ⭐ 마진 계산의 핵심
Saleprice DECIMAL -- 판매가
SUNG_CODE NVARCHAR(9) -- 성분코드
POS_BOON NVARCHAR(6) -- 분류 (010103=동물약)
GoodsSelCode NVARCHAR(2) -- 사용여부 (B=사용, !=미사용)
```
### 2.2 바코드 매핑
#### CD_BARCODE - 표준코드 ↔ 바코드 매핑
```sql
BARCODE NVARCHAR(20) -- 바코드
BASECODE NVARCHAR(10) -- 표준코드 (EDI 코드)
DRUGCODE NVARCHAR(10) -- 제품코드
TITLECODE NVARCHAR(20) -- 대표코드
```
#### CD_ITEM_UNIT_MEMBER - 단위별 바코드 (낱개/박스)
```sql
DRUGCODE NVARCHAR(20) -- 제품코드
CD_CD_UNIT NVARCHAR(3) -- 단위코드
CD_NM_UNIT REAL -- 단위수량
CD_CD_BARCODE NVARCHAR(20) -- 단위 바코드 ⭐ APC 코드 (02로 시작)
CD_MY_UNIT DECIMAL -- 단위가격
```
> **APC 코드**: `02`로 시작하는 13자리 바코드 = 동물약 식별 코드
> 예: CD_ITEM_UNIT_MEMBER에서 `CD_CD_BARCODE LIKE '02%'`
### 2.3 재고 테이블
#### IM_total - 현재 재고
```sql
DrugCode NVARCHAR(12) -- 제품코드
IM_QT_sale_debit FLOAT -- 현재 재고 수량
```
#### IM_date_total - 일별 재고 변동
```sql
IM_DT_appl NVARCHAR(8) -- 날짜 (YYYYMMDD)
DrugCode NVARCHAR(12) -- 제품코드
IM_QT_sale_credit DECIMAL -- 입고량
im_qt_sale_debit DECIMAL -- 출고량
```
### 2.4 입고 테이블 (PM_DRUG)
#### WH_main - 입고 마스터
```sql
WH_NO_stock NVARCHAR(14) -- 입고번호 (PK)
WH_DT_appl NVARCHAR(8) -- 입고일 (YYYYMMDD)
WH_CD_cust_sale NVARCHAR(10) -- 도매상코드 (→ PM_BASE.CD_custom)
WH_BUSINAME NVARCHAR(200) -- 도매상명
WH_MY_amount_t DECIMAL -- 입고 총액
```
#### WH_sub - 입고 상세
```sql
WH_SR_stock NVARCHAR(14) -- 입고번호 (FK → WH_main)
DrugCode NVARCHAR(20) -- 제품코드
WH_DT_appl NVARCHAR(8) -- 입고일
WH_NM_item_a DECIMAL -- 입고 수량 ⭐
WH_MY_unit_a DECIMAL -- 입고 단가 ⭐
WH_MY_amount_a DECIMAL -- 입고 금액 (수량 × 단가)
WH_END_validity NVARCHAR(8) -- 유효기한
WH_LOT_NO NVARCHAR(20) -- LOT 번호
```
### 2.5 판매 테이블 (PM_PRES)
#### SALE_MAIN - 판매 마스터
```sql
SL_NO_order NVARCHAR(14) -- 거래번호 (PK)
SL_DT_appl NVARCHAR(8) -- 판매일
SL_CD_custom NVARCHAR(10) -- 고객코드
InsertTime DATETIME -- 등록시간
SL_MY_total DECIMAL -- 총액
SL_MY_sale DECIMAL -- 판매액 ⭐
SL_MY_sale_cost DECIMAL -- 원가 합계 ⭐ (마진 계산용)
SL_MY_discount DECIMAL -- 할인액
```
#### SALE_SUB - 판매 상세
```sql
SL_NO_order NVARCHAR(14) -- 거래번호 (FK)
DrugCode NVARCHAR(20) -- 제품코드
SL_NM_item DECIMAL -- 판매 수량
SL_NM_cost_a DECIMAL -- 판매 단가
SL_TOTAL_PRICE DECIMAL -- 판매 금액 (수량 × 단가)
INPRICE DECIMAL -- 입고가 ⭐⭐⭐ 마진 계산의 핵심!
SL_MY_in_cost DECIMAL -- 입고 원가 (= INPRICE)
SL_INPUT_PRICE DECIMAL -- 입력가격
BARCODE NVARCHAR(20) -- 판매 시 사용된 바코드
```
---
## 3. 제품/바코드 구조
### 3.1 코드 체계
```
제품코드 (DrugCode)
├── 전문의약품: 9자리 숫자 (예: 652606580)
├── 일반의약품: 9자리 숫자
└── 자체등록: LB로 시작 (예: LB000003778)
바코드 종류
├── 대표바코드: CD_GOODS.BARCODE
├── 단위바코드: CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE
├── APC 코드: 02/92로 시작하는 13자리 (동물약)
└── 표준코드: CD_BARCODE.BASECODE (EDI 연동용)
```
### 3.2 바코드 조회 순서
```sql
-- 제품의 바코드 찾기 (우선순위)
1. CD_GOODS.BARCODE -- 대표 바코드
2. CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE -- 단위 바코드 (낱개/박스)
3. CD_BARCODE.BARCODE -- 표준코드 매핑
```
---
## 4. 입고 흐름
### 4.1 입고 기록 생성
```
도매상 발주 → 입고 등록
┌─────────────┐
│ WH_main │ 입고 마스터 생성
│ (입고번호) │ - 도매상코드
└─────┬───────┘ - 입고일
┌─────────────┐
│ WH_sub │ 입고 상세 생성
│ (품목별) │ - 제품코드
└─────┬───────┘ - 수량, 단가 ⭐
┌─────────────┐
│ IM_total │ 재고 증가
└─────────────┘
```
### 4.2 입고 시 가격 업데이트
입고 처리 시 **CD_GOODS.Price** (입고가)가 업데이트될 수 있음:
```sql
-- 최근 입고 단가로 CD_GOODS.Price 업데이트 (POS 설정에 따름)
UPDATE CD_GOODS
SET Price = (최근 입고 단가)
WHERE DrugCode = ?
```
> **중요**: POS 설정에 따라 입고 시 자동 업데이트 여부가 결정됨
---
## 5. 판매 흐름
### 5.1 판매 기록 생성
```
바코드 스캔 → 판매 등록
┌─────────────┐
│ SALE_MAIN │ 판매 마스터 생성
│ (거래번호) │ - 고객코드, 날짜
└─────┬───────┘ - 총액, 원가합계 (SL_MY_sale_cost)
┌─────────────┐
│ SALE_SUB │ 판매 상세 생성 (품목별)
│ │ - 제품코드
│ │ - 판매가 (SL_NM_cost_a)
│ │ - 입고가 (INPRICE) ⭐
└─────┬───────┘
┌─────────────┐
│ IM_total │ 재고 감소
└─────────────┘
```
### 5.2 판매 시 INPRICE 설정 (핵심!)
**판매 시 SALE_SUB.INPRICE는 CD_GOODS.Price에서 가져옴**
```sql
-- 판매 등록 시 (POS 시스템 내부 로직 추정)
INSERT INTO SALE_SUB (
SL_NO_order, DrugCode, SL_NM_item, SL_NM_cost_a,
INPRICE, -- CD_GOODS.Price 값 사용
...
) VALUES (
@거래번호, @제품코드, @수량, @판매단가,
(SELECT Price FROM CD_GOODS WHERE DrugCode = @제품코드),
...
)
```
> **실제 확인 결과**: SALE_SUB.INPRICE ≈ CD_GOODS.Price (99% 일치)
---
## 6. 마진 계산 로직
### 6.1 거래별 마진 계산
```sql
-- SALE_MAIN에서 마진 계산
마진액 = SL_MY_sale (판매액) - SL_MY_sale_cost (원가합계)
마진율 = (판매액 - 원가합계) / 판매액 × 100
-- SL_MY_sale_cost 계산 (내부 로직)
SL_MY_sale_cost = SUM(SALE_SUB.INPRICE × SALE_SUB.SL_NM_item)
-- 입고가 × 판매수량의 합계
```
### 6.2 품목별 마진 계산
```sql
-- SALE_SUB에서 품목별 마진
품목_마진 = SL_TOTAL_PRICE - (INPRICE × SL_NM_item)
= 판매금액 - (입고가 × 수량)
```
### 6.3 마진 계산 예시
```
거래 20260313000076:
├── 판매액: 64,500원
├── 원가: 31,650원 ← SALE_SUB.INPRICE 합계
├── 마진: 32,850원
└── 마진율: 50.9%
```
---
## 7. 문제점: 입고 없이 판매 시 마진 0
### 7.1 문제 원인
**INPRICE = 0이 되는 경우:**
1. **CD_GOODS.Price가 0 또는 NULL인 경우**
- 제품 등록 시 입고가를 설정하지 않음
- 입고 기록 없이 제품만 등록
2. **POS 시스템 특수 케이스**
- 일부 상황에서 INPRICE가 0으로 설정됨
- (정확한 조건은 POS 내부 로직에 따름)
### 7.2 실제 데이터 분석 (2026-03-13 기준)
```
최근 1개월 판매 건수: 1,767건
├── INPRICE > 0: 1,749건 (98.98%)
└── INPRICE = 0: 18건 (1.02%) ← 마진 0 문제 발생!
```
### 7.3 INPRICE=0 사례 분석
| 제품코드 | 제품명 | CD_GOODS.Price | SALE_SUB.INPRICE | 입고기록 |
|----------|--------|----------------|------------------|----------|
| LB000003658 | 수리팍(제로슈거) | 1,450 | **0** | 있음 |
| LB000003575 | 알파플러스정 | 1 | **0** | 있음 |
| LB000001822 | 헤파토스시럽 | 1,613 | **0** | 있음 |
> **특이사항**: 입고 기록이 있고 CD_GOODS.Price도 있는데 INPRICE=0인 경우 존재
> → POS 특수 상황에서 발생 (추가 조사 필요)
### 7.4 해결 방안
#### 방안 1: 제품 등록 시 입고가 필수 입력
```sql
-- 제품 등록 시 Price 필수 체크
IF Price IS NULL OR Price = 0 THEN
ERROR '입고가를 입력하세요'
```
#### 방안 2: 판매 전 입고 기록 확인
```sql
-- 판매 시 입고 기록 확인
IF NOT EXISTS (SELECT 1 FROM WH_sub WHERE DrugCode = @코드) THEN
WARNING '입고 기록 없음. 마진 계산 불가'
```
#### 방안 3: INPRICE 자동 채우기
```sql
-- INPRICE=0인 경우 CD_GOODS.Price로 업데이트
UPDATE SALE_SUB
SET INPRICE = (SELECT Price FROM CD_GOODS WHERE DrugCode = SALE_SUB.DrugCode)
WHERE INPRICE = 0
```
---
## 8. 관련 API 목록 (app.py)
| 엔드포인트 | 기능 | 사용 테이블 |
|------------|------|-------------|
| `/api/products` | 제품 검색 | CD_GOODS, IM_total, CD_ITEM_UNIT_MEMBER |
| `/api/drugs/<code>/purchase-history` | 입고 이력 | WH_main, WH_sub |
| `/api/sales-detail` | 판매 상세 | SALE_SUB, CD_GOODS |
| `/api/usage` | 기간별 사용량 | SALE_SUB, CD_GOODS |
| `/api/rx-usage` | 처방 사용량 | PS_sub_pharm, PS_main |
| `/admin/transaction/<id>` | 거래 상세 | SALE_MAIN, SALE_SUB |
---
## 9. 테이블 관계도
```
PM_DRUG PM_PRES
======== ========
CD_GOODS ────────────────────────┐
│ DrugCode (PK) │
│ │
├── CD_ITEM_UNIT_MEMBER │
│ (단위바코드) │
│ │
├── CD_BARCODE │
│ (표준코드 매핑) │
│ │
├── IM_total │
│ (현재 재고) │
│ │
├── WH_sub ◄─── WH_main │
│ (입고 상세) (입고 마스터)│
│ │
└──────────────────────────────┼──► SALE_SUB ◄─── SALE_MAIN
│ (판매 상세) (판매 마스터)
│ │
│ │ INPRICE = CD_GOODS.Price
│ │
└─────────┘
마진 계산:
SALE_MAIN.SL_MY_sale_cost = Σ(SALE_SUB.INPRICE × 수량)
마진 = SL_MY_sale - SL_MY_sale_cost
```
---
## 10. 핵심 요약
### 10.1 마진 계산 흐름
```
입고 등록 (WH_sub)
CD_GOODS.Price 업데이트 (입고가)
판매 등록 (SALE_SUB)
SALE_SUB.INPRICE ← CD_GOODS.Price ⭐
SALE_MAIN.SL_MY_sale_cost = Σ(INPRICE × 수량)
마진 = 판매액 - 원가
```
### 10.2 문제 원인
- **INPRICE = 0**이면 마진 = 판매액 (100% 마진처럼 보이지만 실제로는 잘못된 데이터)
- **CD_GOODS.Price = 0**이면 판매 시 INPRICE도 0
### 10.3 권장 조치
1. 제품 등록 시 입고가(Price) 필수 입력 강제
2. 입고 처리 후 판매 권장 (입고 기록 없으면 경고)
3. 마진 리포트에서 INPRICE=0인 건 별도 표시/경고
---
*분석: 용림 (Yongrim) | 2026-03-13*