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

12 KiB
Raw Blame History

약국 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

-- 핵심 컬럼
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 - 표준코드 ↔ 바코드 매핑

BARCODE        NVARCHAR(20)   -- 바코드
BASECODE       NVARCHAR(10)   -- 표준코드 (EDI 코드)
DRUGCODE       NVARCHAR(10)   -- 제품코드
TITLECODE      NVARCHAR(20)   -- 대표코드

CD_ITEM_UNIT_MEMBER - 단위별 바코드 (낱개/박스)

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 - 현재 재고

DrugCode           NVARCHAR(12)   -- 제품코드
IM_QT_sale_debit   FLOAT          -- 현재 재고 수량

IM_date_total - 일별 재고 변동

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 - 입고 마스터

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 - 입고 상세

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 - 판매 마스터

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 - 판매 상세

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 바코드 조회 순서

-- 제품의 바코드 찾기 (우선순위)
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 (입고가)가 업데이트될 수 있음:

-- 최근 입고 단가로 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에서 가져옴

-- 판매 등록 시 (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 거래별 마진 계산

-- 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 품목별 마진 계산

-- 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: 제품 등록 시 입고가 필수 입력

-- 제품 등록 시 Price 필수 체크
IF Price IS NULL OR Price = 0 THEN
    ERROR '입고가를 입력하세요'

방안 2: 판매 전 입고 기록 확인

-- 판매 시 입고 기록 확인
IF NOT EXISTS (SELECT 1 FROM WH_sub WHERE DrugCode = @코드) THEN
    WARNING '입고 기록 없음. 마진 계산 불가'

방안 3: INPRICE 자동 채우기

-- 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