✨ 주요 기능 - 환자 관리: 환자 등록 및 조회 (이름, 전화번호, 주민번호, 성별) - 입고 관리: 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>
230 lines
9.9 KiB
SQL
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; |