feat: 처방 관리 및 재고 원장 시스템 구현

## 처방 관리 (조제) 기능
- compounds API 추가 (목록/상세/환자별 조회)
- 조제 시 자동 재고 차감 (FIFO)
- 조제 내역 UI (EMR 스타일)
- 조제 상세보기 모달 (처방구성, 재고소비내역)
- 오늘/이번달 조제 통계 표시

## 재고 원장 시스템
- stock-ledger API 구현
- 입출고 내역 실시간 추적
- 재고 현황 페이지 개선 (통계 카드 추가)
- 입출고 원장 모달 UI
- 약재별/전체 입출고 내역 조회

## 확인된 동작
- 박주호 환자 오미자 200g 조제
- 재고 2000g → 1800g 정확히 차감
- 모든 입출고 stock_ledger에 기록

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-02-15 11:21:20 +00:00
parent 63128fdccb
commit 38838e5ecf
18 changed files with 1835 additions and 46 deletions

View File

@@ -408,53 +408,289 @@
</div>
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">조제 내역</h5>
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-clipboard-pulse"></i> 조제 내역</h5>
<div>
<span class="badge bg-primary me-2">오늘 조제: <span id="todayCompoundCount">0</span></span>
<span class="badge bg-info">이번달 조제: <span id="monthCompoundCount">0</span></span>
</div>
</div>
</div>
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>조제일</th>
<th>환자명</th>
<th>처방명</th>
<th>제수</th>
<th>파우치</th>
<th>원가</th>
<th>상태</th>
</tr>
</thead>
<tbody id="compoundsList">
<!-- Dynamic content -->
</tbody>
</table>
<div class="row mb-3">
<div class="col-md-4">
<input type="date" class="form-control" id="compoundDateFilter" placeholder="날짜 선택">
</div>
<div class="col-md-4">
<input type="text" class="form-control" id="compoundPatientFilter" placeholder="환자명 검색">
</div>
<div class="col-md-4">
<button class="btn btn-outline-secondary" id="refreshCompoundsBtn">
<i class="bi bi-arrow-clockwise"></i> 새로고침
</button>
<button class="btn btn-outline-info ms-2" id="exportCompoundsBtn">
<i class="bi bi-download"></i> 엑셀 다운로드
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead class="table-dark">
<tr>
<th width="40">#</th>
<th width="100">조제일시</th>
<th width="80">환자명</th>
<th width="100">연락처</th>
<th>처방명</th>
<th width="60">제수</th>
<th width="60">첩수</th>
<th width="80">파우치</th>
<th width="100">원가</th>
<th width="100">판매가</th>
<th width="80">상태</th>
<th width="100">처방전번호</th>
<th width="120">작업</th>
</tr>
</thead>
<tbody id="compoundsList">
<!-- Dynamic content -->
</tbody>
</table>
</div>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="compoundsPagination">
<!-- Dynamic pagination -->
</ul>
</nav>
</div>
</div>
<!-- 조제 상세보기 모달 -->
<div class="modal fade" id="compoundDetailModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title"><i class="bi bi-file-medical"></i> 조제 상세 정보</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-person"></i> 환자 정보</h6>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">환자명:</dt>
<dd class="col-sm-8" id="detailPatientName"></dd>
<dt class="col-sm-4">연락처:</dt>
<dd class="col-sm-8" id="detailPatientPhone"></dd>
<dt class="col-sm-4">조제일:</dt>
<dd class="col-sm-8" id="detailCompoundDate"></dd>
</dl>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-journal-medical"></i> 처방 정보</h6>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">처방명:</dt>
<dd class="col-sm-8" id="detailFormulaName"></dd>
<dt class="col-sm-4">처방전번호:</dt>
<dd class="col-sm-8" id="detailPrescriptionNo"></dd>
<dt class="col-sm-4">제수/첩수/파우치:</dt>
<dd class="col-sm-8" id="detailQuantities"></dd>
</dl>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-list-ul"></i> 처방 구성 약재</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>약재명</th>
<th>보험코드</th>
<th>1첩당 용량</th>
<th>총 사용량</th>
<th>비고</th>
</tr>
</thead>
<tbody id="detailIngredients">
<!-- Dynamic content -->
</tbody>
</table>
</div>
</div>
<div class="card mt-3">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="bi bi-box-seam"></i> 재고 소비 내역</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>약재명</th>
<th>원산지</th>
<th>공급처</th>
<th>사용량</th>
<th>단가</th>
<th>금액</th>
</tr>
</thead>
<tbody id="detailConsumptions">
<!-- Dynamic content -->
</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end">총 원가:</th>
<th id="detailTotalCost"></th>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<label>비고:</label>
<p id="detailNotes" class="form-control-plaintext"></p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info" id="printCompoundBtn">
<i class="bi bi-printer"></i> 인쇄
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
</div>
<!-- Inventory Page -->
<div id="inventory" class="main-content">
<h3 class="mb-4">재고 현황</h3>
<div class="d-flex justify-content-between align-items-center mb-4">
<h3><i class="bi bi-box-seam"></i> 재고 현황</h3>
<button class="btn btn-outline-info" id="showStockLedgerBtn">
<i class="bi bi-journal-text"></i> 입출고 원장
</button>
</div>
<div class="row mb-3">
<div class="col-md-3">
<div class="stat-card bg-primary text-white">
<h6>총 재고 금액</h6>
<h4 id="totalInventoryValue">₩0</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-success text-white">
<h6>재고 보유 약재</h6>
<h4 id="totalHerbsInStock">0종</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-warning text-white">
<h6>오늘 입고</h6>
<h4 id="todayPurchases">0건</h4>
</div>
</div>
<div class="col-md-3">
<div class="stat-card bg-info text-white">
<h6>오늘 출고</h6>
<h4 id="todayConsumptions">0건</h4>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="mb-3">
<input type="text" class="form-control" id="inventorySearch" placeholder="약재명으로 검색...">
</div>
<table class="table table-hover">
<thead>
<tr>
<th>보험코드</th>
<th>약재명</th>
<th>현재 재고(g)</th>
<th>로트 수</th>
<th>평균 단가</th>
<th>재고 금액</th>
</tr>
</thead>
<tbody id="inventoryList">
<!-- Dynamic content -->
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>보험코드</th>
<th>약재명</th>
<th>현재 재고(g)</th>
<th>로트 수</th>
<th>평균 단가</th>
<th>재고 금액</th>
<th>작업</th>
</tr>
</thead>
<tbody id="inventoryList">
<!-- Dynamic content -->
</tbody>
</table>
</div>
</div>
</div>
<!-- 재고 원장 모달 -->
<div class="modal fade" id="stockLedgerModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title"><i class="bi bi-journal-text"></i> 입출고 원장</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-6">
<select class="form-control" id="ledgerHerbFilter">
<option value="">전체 약재</option>
</select>
</div>
<div class="col-md-6">
<select class="form-control" id="ledgerTypeFilter">
<option value="">전체 내역</option>
<option value="PURCHASE">입고만</option>
<option value="CONSUME">출고만</option>
<option value="RECEIPT">입고접수</option>
</select>
</div>
</div>
<div class="table-responsive" style="max-height: 500px; overflow-y: auto;">
<table class="table table-sm table-striped">
<thead class="table-dark sticky-top">
<tr>
<th>일시</th>
<th>구분</th>
<th>약재명</th>
<th>수량(g)</th>
<th>단가</th>
<th>원산지</th>
<th>공급처/환자</th>
<th>참조번호</th>
</tr>
</thead>
<tbody id="stockLedgerList">
<!-- Dynamic content -->
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
</div>
@@ -462,24 +698,68 @@
<!-- Herbs Page -->
<div id="herbs" class="main-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3>약재 관리</h3>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#herbModal">
<i class="bi bi-plus-circle"></i> 새 약재 등록
</button>
<h3>약재 관리 <small class="text-muted">(주성분코드 기준)</small></h3>
<div>
<div class="btn-group me-2" role="group">
<button type="button" class="btn btn-outline-primary active" data-filter="all">
<i class="bi bi-list"></i> 전체
</button>
<button type="button" class="btn btn-outline-success" data-filter="stock">
<i class="bi bi-check-circle"></i> 재고 있음
</button>
<button type="button" class="btn btn-outline-secondary" data-filter="no-stock">
<i class="bi bi-x-circle"></i> 재고 없음
</button>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#herbModal">
<i class="bi bi-plus-circle"></i> 새 약재 등록
</button>
</div>
</div>
<!-- 통계 정보 -->
<div id="herbMasterSummary" class="alert alert-info mb-3">
<!-- Dynamic summary -->
</div>
<!-- 검색 바 -->
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="herbSearch"
placeholder="약재명 또는 주성분코드로 검색...">
</div>
<div class="col-md-6">
<select class="form-select" id="efficacyFilter">
<option value="">모든 효능</option>
<option value="보혈">보혈</option>
<option value="보기">보기</option>
<option value="활혈">활혈</option>
<option value="온중">온중</option>
<option value="청열">청열</option>
</select>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th>보험코드</th>
<th>주성분코드</th>
<th>약재명</th>
<th>규격</th>
<th>현재 재고</th>
<th>효능</th>
<th>재고</th>
<th>평균단가</th>
<th>제품수</th>
<th>상태</th>
<th>작업</th>
</tr>
</thead>
<tbody id="herbsList">
<tbody id="herbMastersList">
<!-- Dynamic content -->
</tbody>
</table>