feat: 실시간 커스텀 처방(가감방) 감지 시스템 구현

- 프론트엔드: 조제 시 실시간 커스텀 처방 감지
  - 처방 선택 시 원래 구성 약재 저장
  - 약재 추가/삭제/변경 시 즉시 감지
  - 가감방 뱃지 및 변경 내용 표시

- 백엔드: 커스텀 처방 자동 감지 및 저장
  - compounds 테이블에 커스텀 관련 필드 추가
  - 조제 시 원 처방과 비교하여 변경사항 자동 감지
  - 커스텀 처방 정보 저장 (추가/제거/변경된 약재)

- 환자 조제 내역에 커스텀 처방 표시
  - 가감방 뱃지 표시
  - 변경 내용 상세 표시

- DB 마이그레이션 스크립트 추가
  - is_custom, custom_summary, custom_type 필드 추가
  - compound_ingredients에 modification_type, original_grams 필드 추가

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-02-17 01:28:44 +00:00
parent d6410fa273
commit 1441c01fb4
6 changed files with 1052 additions and 12 deletions

View File

@@ -1,5 +1,8 @@
// 한약 재고관리 시스템 - Frontend JavaScript
// 원래 처방 구성 저장용 전역 변수
let originalFormulaIngredients = {};
$(document).ready(function() {
// 페이지 네비게이션
$('.sidebar .nav-link').on('click', function(e) {
@@ -273,13 +276,22 @@ $(document).ready(function() {
const detailRowId = `compound-detail-${compound.compound_id}`;
// 처방명 표시 (가감방 여부 포함)
let formulaDisplay = compound.formula_name || '직접조제';
if (compound.is_custom && compound.formula_name) {
formulaDisplay = `${compound.formula_name} <span class="badge bg-warning ms-1">가감</span>`;
}
tbody.append(`
<tr class="compound-row" style="cursor: pointer;" data-compound-id="${compound.compound_id}">
<td>
<i class="bi bi-chevron-right toggle-icon"></i>
</td>
<td>${compound.compound_date || '-'}</td>
<td>${compound.formula_name || '직접조제'}</td>
<td>
${formulaDisplay}
${compound.custom_summary ? `<br><small class="text-muted">${compound.custom_summary}</small>` : ''}
</td>
<td>${compound.je_count || 0}</td>
<td>${compound.cheop_total || 0}</td>
<td>${compound.pouch_total || 0}</td>
@@ -573,6 +585,11 @@ $(document).ready(function() {
// 처방 선택 시 구성 약재 로드
$('#compoundFormula').on('change', function() {
const formulaId = $(this).val();
// 원래 처방 구성 초기화
originalFormulaIngredients = {};
$('#customPrescriptionBadge').remove(); // 커스텀 뱃지 제거
if (!formulaId) {
$('#compoundIngredients').empty();
return;
@@ -591,6 +608,14 @@ $(document).ready(function() {
if (response.success) {
$('#compoundIngredients').empty();
// 원래 처방 구성 저장
response.data.forEach(ing => {
originalFormulaIngredients[ing.ingredient_code] = {
herb_name: ing.herb_name,
grams_per_cheop: ing.grams_per_cheop
};
});
response.data.forEach(ing => {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const totalGrams = ing.grams_per_cheop * cheopTotal;
@@ -688,6 +713,7 @@ $(document).ready(function() {
// 삭제 버튼 이벤트
$('.remove-compound-ingredient').on('click', function() {
$(this).closest('tr').remove();
updateIngredientTotals(); // 총용량 재계산 및 커스텀 감지
});
}
});
@@ -704,6 +730,80 @@ $(document).ready(function() {
});
checkStockForCompound();
// 커스텀 처방 감지 호출
checkCustomPrescription();
}
// 커스텀 처방 감지 함수
function checkCustomPrescription() {
const formulaId = $('#compoundFormula').val();
// 처방이 선택되지 않았거나 직접조제인 경우 리턴
if (!formulaId || formulaId === 'custom' || Object.keys(originalFormulaIngredients).length === 0) {
$('#customPrescriptionBadge').remove();
return;
}
// 현재 약재 구성 수집
const currentIngredients = {};
$('#compoundIngredients tr').each(function() {
const ingredientCode = $(this).attr('data-ingredient-code');
const gramsPerCheop = parseFloat($(this).find('.grams-per-cheop').val());
if (ingredientCode && gramsPerCheop > 0) {
currentIngredients[ingredientCode] = gramsPerCheop;
}
});
// 변경사항 감지
const customDetails = [];
let isCustom = false;
// 추가된 약재 확인
for (const code in currentIngredients) {
if (!originalFormulaIngredients[code]) {
const herbName = $(`tr[data-ingredient-code="${code}"] .herb-select-compound option:selected`).data('herb-name') || code;
customDetails.push(`${herbName} ${currentIngredients[code]}g 추가`);
isCustom = true;
}
}
// 삭제된 약재 확인
for (const code in originalFormulaIngredients) {
if (!currentIngredients[code]) {
customDetails.push(`${originalFormulaIngredients[code].herb_name} 제거`);
isCustom = true;
}
}
// 용량 변경된 약재 확인
for (const code in currentIngredients) {
if (originalFormulaIngredients[code]) {
const originalGrams = originalFormulaIngredients[code].grams_per_cheop;
const currentGrams = currentIngredients[code];
if (Math.abs(originalGrams - currentGrams) > 0.01) {
const herbName = originalFormulaIngredients[code].herb_name;
customDetails.push(`${herbName} ${originalGrams}g→${currentGrams}g`);
isCustom = true;
}
}
}
// 커스텀 뱃지 표시/숨기기
$('#customPrescriptionBadge').remove();
if (isCustom) {
const badgeHtml = `
<div id="customPrescriptionBadge" class="alert alert-warning mt-2">
<span class="badge bg-warning">가감방</span>
<small class="ms-2">${customDetails.join(' | ')}</small>
</div>
`;
// 처방 선택 영역 아래에 추가
$('#compoundFormula').closest('.col-md-6').append(badgeHtml);
}
}
// 재고 확인
@@ -861,6 +961,7 @@ $(document).ready(function() {
newRow.find('.grams-per-cheop').on('input', updateIngredientTotals);
newRow.find('.remove-compound-ingredient').on('click', function() {
$(this).closest('tr').remove();
updateIngredientTotals(); // 총용량 재계산 및 커스텀 감지
});
newRow.find('.herb-select-compound').on('change', function() {
const herbId = $(this).val();