kdrug-inventory-system/database/schema.sql
시골약사 2fddc89bca 초기 커밋: 한약 재고관리 시스템
 주요 기능
- 환자 관리: 환자 등록 및 조회 (이름, 전화번호, 주민번호, 성별)
- 입고 관리: Excel 파일 업로드로 대량 입고 처리
- 처방 관리: 약속 처방 템플릿 등록 및 관리
- 조제 관리: 처방 기반 조제 및 약재 가감 기능
- 재고 관리: 실시간 재고 현황 및 로트별 관리

🛠️ 기술 스택
- Backend: Flask (Python 웹 프레임워크)
- Database: SQLite (경량 관계형 데이터베이스)
- Frontend: Bootstrap + jQuery
- Excel 처리: pandas + openpyxl

🔧 핵심 개념
- 1제 = 20첩 = 30파우치 (기본값)
- FIFO 방식 재고 차감
- 로트별 원산지/단가 관리
- 정확한 조제 원가 계산

📁 프로젝트 구조
- app.py: Flask 백엔드 서버
- database/: 데이터베이스 스키마 및 파일
- templates/: HTML 템플릿
- static/: JavaScript 및 CSS
- sample/: 샘플 Excel 파일

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-15 07:57:40 +00:00

230 lines
9.9 KiB
SQL

-- 한약 재고관리 시스템 데이터베이스 스키마
-- SQLite 기준
-- 1) 도매상/공급업체
CREATE TABLE IF NOT EXISTS suppliers (
supplier_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
business_no TEXT,
contact_person TEXT,
phone TEXT,
address TEXT,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 2) 약재 마스터 (보험코드 9자리 기준)
CREATE TABLE IF NOT EXISTS herb_items (
herb_item_id INTEGER PRIMARY KEY AUTOINCREMENT,
insurance_code TEXT UNIQUE, -- 보험코드 (9자리)
herb_name TEXT NOT NULL, -- 약재명
specification TEXT, -- 규격/품질
default_unit TEXT DEFAULT 'g', -- 기본 단위
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 3) 환자 정보
CREATE TABLE IF NOT EXISTS patients (
patient_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL,
jumin_no TEXT, -- 주민번호 (암호화 필요)
gender TEXT CHECK(gender IN ('M', 'F')),
birth_date DATE,
address TEXT,
notes TEXT,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(phone, name)
);
-- 4) 입고장 헤더
CREATE TABLE IF NOT EXISTS purchase_receipts (
receipt_id INTEGER PRIMARY KEY AUTOINCREMENT,
supplier_id INTEGER NOT NULL,
receipt_date DATE NOT NULL,
receipt_no TEXT, -- 입고 번호/전표번호
vat_included INTEGER DEFAULT 1, -- 부가세 포함 여부
vat_rate REAL DEFAULT 0.10, -- 부가세율
total_amount REAL, -- 총 입고액
source_file TEXT, -- Excel 파일명
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id)
);
-- 5) 입고장 상세 라인
CREATE TABLE IF NOT EXISTS purchase_receipt_lines (
line_id INTEGER PRIMARY KEY AUTOINCREMENT,
receipt_id INTEGER NOT NULL,
herb_item_id INTEGER NOT NULL,
origin_country TEXT, -- 원산지
quantity_g REAL NOT NULL, -- 구입량(g)
unit_price_per_g REAL NOT NULL, -- g당 단가 (VAT 포함)
line_total REAL, -- 라인 총액
expiry_date DATE, -- 유효기간
lot_number TEXT, -- 로트번호
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (receipt_id) REFERENCES purchase_receipts(receipt_id),
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id)
);
-- 6) 재고 로트 (입고 라인별 재고 관리)
CREATE TABLE IF NOT EXISTS inventory_lots (
lot_id INTEGER PRIMARY KEY AUTOINCREMENT,
herb_item_id INTEGER NOT NULL,
supplier_id INTEGER NOT NULL,
receipt_line_id INTEGER NOT NULL,
received_date DATE NOT NULL,
origin_country TEXT,
unit_price_per_g REAL NOT NULL,
quantity_received REAL NOT NULL, -- 입고 수량
quantity_onhand REAL NOT NULL, -- 현재 재고
expiry_date DATE,
lot_number TEXT,
is_depleted INTEGER DEFAULT 0, -- 소진 여부
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id),
FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id),
FOREIGN KEY (receipt_line_id) REFERENCES purchase_receipt_lines(line_id)
);
-- 7) 재고 원장 (모든 재고 변동 기록)
CREATE TABLE IF NOT EXISTS stock_ledger (
ledger_id INTEGER PRIMARY KEY AUTOINCREMENT,
event_time DATETIME DEFAULT CURRENT_TIMESTAMP,
event_type TEXT NOT NULL CHECK(event_type IN ('RECEIPT', 'CONSUME', 'ADJUST', 'DISCARD', 'RETURN')),
herb_item_id INTEGER NOT NULL,
lot_id INTEGER,
quantity_delta REAL NOT NULL, -- 증감량 (+입고, -사용)
unit_cost_per_g REAL,
reference_table TEXT, -- 참조 테이블 (compounds, adjustments 등)
reference_id INTEGER, -- 참조 ID
notes TEXT,
created_by TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id),
FOREIGN KEY (lot_id) REFERENCES inventory_lots(lot_id)
);
-- 8) 처방 마스터 (약속 처방)
CREATE TABLE IF NOT EXISTS formulas (
formula_id INTEGER PRIMARY KEY AUTOINCREMENT,
formula_code TEXT UNIQUE, -- 처방 코드
formula_name TEXT NOT NULL, -- 처방명 (예: 쌍화탕)
formula_type TEXT DEFAULT 'CUSTOM', -- INSURANCE(보험), CUSTOM(약속처방)
base_cheop INTEGER DEFAULT 20, -- 기본 첩수 (1제 기준)
base_pouches INTEGER DEFAULT 30, -- 기본 파우치수 (1제 기준)
description TEXT,
is_active INTEGER DEFAULT 1,
created_by TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 9) 처방 구성 약재
CREATE TABLE IF NOT EXISTS formula_ingredients (
ingredient_id INTEGER PRIMARY KEY AUTOINCREMENT,
formula_id INTEGER NOT NULL,
herb_item_id INTEGER NOT NULL,
grams_per_cheop REAL NOT NULL, -- 1첩당 그램수
notes TEXT,
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (formula_id) REFERENCES formulas(formula_id),
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id),
UNIQUE (formula_id, herb_item_id)
);
-- 10) 조제 작업 (처방 실행)
CREATE TABLE IF NOT EXISTS compounds (
compound_id INTEGER PRIMARY KEY AUTOINCREMENT,
patient_id INTEGER,
formula_id INTEGER,
compound_date DATE NOT NULL,
je_count REAL NOT NULL, -- 제수 (1제, 0.5제 등)
cheop_total REAL NOT NULL, -- 총 첩수
pouch_total REAL NOT NULL, -- 총 파우치수
cost_total REAL, -- 원가 총액
sell_price_total REAL, -- 판매 총액
prescription_no TEXT, -- 처방전 번호
status TEXT DEFAULT 'PREPARED', -- PREPARED, DISPENSED, CANCELLED
notes TEXT,
created_by TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (patient_id) REFERENCES patients(patient_id),
FOREIGN KEY (formula_id) REFERENCES formulas(formula_id)
);
-- 11) 조제 약재 구성 (실제 조제시 사용된 약재 - 가감 포함)
CREATE TABLE IF NOT EXISTS compound_ingredients (
compound_ingredient_id INTEGER PRIMARY KEY AUTOINCREMENT,
compound_id INTEGER NOT NULL,
herb_item_id INTEGER NOT NULL,
grams_per_cheop REAL NOT NULL, -- 1첩당 그램수 (가감 반영)
total_grams REAL NOT NULL, -- 총 사용량
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (compound_id) REFERENCES compounds(compound_id),
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id)
);
-- 12) 조제 소비 내역 (로트별 차감)
CREATE TABLE IF NOT EXISTS compound_consumptions (
consumption_id INTEGER PRIMARY KEY AUTOINCREMENT,
compound_id INTEGER NOT NULL,
herb_item_id INTEGER NOT NULL,
lot_id INTEGER NOT NULL,
quantity_used REAL NOT NULL, -- 사용량(g)
unit_cost_per_g REAL NOT NULL, -- 단가
cost_amount REAL, -- 원가액
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (compound_id) REFERENCES compounds(compound_id),
FOREIGN KEY (herb_item_id) REFERENCES herb_items(herb_item_id),
FOREIGN KEY (lot_id) REFERENCES inventory_lots(lot_id)
);
-- 인덱스 생성
CREATE INDEX IF NOT EXISTS idx_herb_items_name ON herb_items(herb_name);
CREATE INDEX IF NOT EXISTS idx_herb_items_code ON herb_items(insurance_code);
CREATE INDEX IF NOT EXISTS idx_inventory_lots_herb ON inventory_lots(herb_item_id, is_depleted);
CREATE INDEX IF NOT EXISTS idx_stock_ledger_herb ON stock_ledger(herb_item_id, event_time);
CREATE INDEX IF NOT EXISTS idx_compounds_patient ON compounds(patient_id);
CREATE INDEX IF NOT EXISTS idx_compounds_date ON compounds(compound_date);
CREATE INDEX IF NOT EXISTS idx_patients_phone ON patients(phone);
-- 뷰 생성 (자주 사용되는 조회)
-- 현재 재고 현황
CREATE VIEW IF NOT EXISTS v_current_stock AS
SELECT
h.herb_item_id,
h.insurance_code,
h.herb_name,
SUM(il.quantity_onhand) as total_quantity,
COUNT(DISTINCT il.lot_id) as lot_count,
AVG(il.unit_price_per_g) as avg_unit_price
FROM herb_items h
LEFT JOIN inventory_lots il ON h.herb_item_id = il.herb_item_id AND il.is_depleted = 0
GROUP BY h.herb_item_id, h.insurance_code, h.herb_name;
-- 처방별 구성 약재 뷰
CREATE VIEW IF NOT EXISTS v_formula_details AS
SELECT
f.formula_id,
f.formula_name,
f.formula_code,
h.herb_name,
fi.grams_per_cheop,
h.insurance_code
FROM formulas f
JOIN formula_ingredients fi ON f.formula_id = fi.formula_id
JOIN herb_items h ON fi.herb_item_id = h.herb_item_id
WHERE f.is_active = 1
ORDER BY f.formula_id, fi.sort_order;