// 한약 재고관리 시스템 - Frontend JavaScript
$(document).ready(function() {
// 페이지 네비게이션
$('.sidebar .nav-link').on('click', function(e) {
e.preventDefault();
const page = $(this).data('page');
// Active 상태 변경
$('.sidebar .nav-link').removeClass('active');
$(this).addClass('active');
// 페이지 전환
$('.main-content').removeClass('active');
$(`#${page}`).addClass('active');
// 페이지별 데이터 로드
loadPageData(page);
});
// 초기 데이터 로드
loadPageData('dashboard');
// 페이지별 데이터 로드 함수
function loadPageData(page) {
switch(page) {
case 'dashboard':
loadDashboard();
break;
case 'patients':
loadPatients();
break;
case 'purchase':
loadPurchaseReceipts();
loadSuppliersForSelect();
break;
case 'formulas':
loadFormulas();
break;
case 'compound':
loadCompounds();
loadPatientsForSelect();
loadFormulasForSelect();
break;
case 'inventory':
loadInventory();
break;
case 'herbs':
loadHerbs();
break;
}
}
// 대시보드 데이터 로드
function loadDashboard() {
// 환자 수
$.get('/api/patients', function(response) {
if (response.success) {
$('#totalPatients').text(response.data.length);
}
});
// 재고 현황
$.get('/api/inventory/summary', function(response) {
if (response.success) {
$('#totalHerbs').text(response.data.length);
$('#inventoryValue').text(formatCurrency(response.summary.total_value));
}
});
// 오늘 조제 수 및 최근 조제 내역
$.get('/api/compounds', function(response) {
if (response.success) {
const today = new Date().toISOString().split('T')[0];
const todayCompounds = response.data.filter(c => c.compound_date === today);
$('#todayCompounds').text(todayCompounds.length);
// 최근 조제 내역 (최근 5개)
const tbody = $('#recentCompounds');
tbody.empty();
const recentCompounds = response.data.slice(0, 5);
if (recentCompounds.length > 0) {
recentCompounds.forEach(compound => {
let statusBadge = '';
switch(compound.status) {
case 'PREPARED':
statusBadge = '조제완료';
break;
case 'DISPENSED':
statusBadge = '출고완료';
break;
case 'CANCELLED':
statusBadge = '취소';
break;
default:
statusBadge = '대기';
}
tbody.append(`
| ${compound.compound_date || '-'} |
${compound.patient_name || '직접조제'} |
${compound.formula_name || '직접조제'} |
${compound.je_count}제 |
${compound.pouch_total}개 |
${statusBadge} |
`);
});
} else {
tbody.html('| 조제 내역이 없습니다. |
');
}
}
});
}
// 환자 목록 로드
function loadPatients() {
$.get('/api/patients', function(response) {
if (response.success) {
const tbody = $('#patientsList');
tbody.empty();
// 각 환자의 처방 횟수를 가져오기 위해 처방 데이터도 로드
$.get('/api/compounds', function(compoundsResponse) {
const compounds = compoundsResponse.success ? compoundsResponse.data : [];
// 환자별 처방 횟수 계산
const compoundCounts = {};
compounds.forEach(compound => {
if (compound.patient_id) {
compoundCounts[compound.patient_id] = (compoundCounts[compound.patient_id] || 0) + 1;
}
});
response.data.forEach(patient => {
const compoundCount = compoundCounts[patient.patient_id] || 0;
tbody.append(`
| ${patient.name} |
${patient.phone} |
${patient.gender === 'M' ? '남' : patient.gender === 'F' ? '여' : '-'} |
${patient.birth_date || '-'} |
${compoundCount}회
|
${patient.notes || '-'} |
|
`);
});
// 처방내역 버튼 이벤트
$('.view-patient-compounds').on('click', function() {
const patientId = $(this).data('id');
const patientName = $(this).data('name');
viewPatientCompounds(patientId, patientName);
});
});
}
});
}
// 환자 등록
$('#savePatientBtn').on('click', function() {
const patientData = {
name: $('#patientName').val(),
phone: $('#patientPhone').val(),
jumin_no: $('#patientJumin').val(),
gender: $('#patientGender').val(),
birth_date: $('#patientBirth').val(),
address: $('#patientAddress').val(),
notes: $('#patientNotes').val()
};
$.ajax({
url: '/api/patients',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(patientData),
success: function(response) {
if (response.success) {
alert('환자가 등록되었습니다.');
$('#patientModal').modal('hide');
$('#patientForm')[0].reset();
loadPatients();
}
},
error: function(xhr) {
alert('오류: ' + xhr.responseJSON.error);
}
});
});
// 환자 처방 내역 조회
function viewPatientCompounds(patientId, patientName) {
// 환자 정보 가져오기
$.get(`/api/patients/${patientId}`, function(patientResponse) {
if (patientResponse.success) {
const patient = patientResponse.data;
// 환자 기본 정보 표시
$('#patientCompoundsName').text(patient.name);
$('#patientInfoName').text(patient.name);
$('#patientInfoPhone').text(patient.phone || '-');
$('#patientInfoGender').text(patient.gender === 'M' ? '남성' : patient.gender === 'F' ? '여성' : '-');
$('#patientInfoBirth').text(patient.birth_date || '-');
// 환자의 처방 내역 가져오기
$.get(`/api/patients/${patientId}/compounds`, function(compoundsResponse) {
if (compoundsResponse.success) {
const compounds = compoundsResponse.compounds || [];
// 통계 계산
const totalCompounds = compounds.length;
let totalJe = 0;
let totalAmount = 0;
let lastVisit = '-';
if (compounds.length > 0) {
compounds.forEach(c => {
totalJe += c.je_count || 0;
totalAmount += c.sell_price_total || 0;
});
lastVisit = compounds[0].compound_date || '-';
}
// 통계 표시
$('#patientTotalCompounds').text(totalCompounds + '회');
$('#patientLastVisit').text(lastVisit);
$('#patientTotalJe').text(totalJe + '제');
$('#patientTotalAmount').text(formatCurrency(totalAmount));
// 처방 내역 테이블 표시
const tbody = $('#patientCompoundsList');
tbody.empty();
if (compounds.length === 0) {
tbody.append(`
| 처방 내역이 없습니다. |
`);
} else {
compounds.forEach((compound, index) => {
// 상태 뱃지
let statusBadge = '';
switch(compound.status) {
case 'PREPARED':
statusBadge = '조제완료';
break;
case 'DISPENSED':
statusBadge = '출고완료';
break;
case 'CANCELLED':
statusBadge = '취소';
break;
default:
statusBadge = '대기';
}
const detailRowId = `compound-detail-${compound.compound_id}`;
tbody.append(`
|
|
${compound.compound_date || '-'} |
${compound.formula_name || '직접조제'} |
${compound.je_count || 0} |
${compound.cheop_total || 0} |
${compound.pouch_total || 0} |
${formatCurrency(compound.cost_total || 0)} |
${formatCurrency(compound.sell_price_total || 0)} |
${statusBadge} |
${compound.prescription_no || '-'} |
|
|
`);
});
// 행 클릭 이벤트 - 상세 정보 토글
$('.compound-row').on('click', function() {
const compoundId = $(this).data('compound-id');
const detailRow = $(`#compound-detail-${compoundId}`);
const icon = $(this).find('.toggle-icon');
if (detailRow.is(':visible')) {
// 닫기
detailRow.slideUp();
icon.removeClass('bi-chevron-down').addClass('bi-chevron-right');
} else {
// 열기 - 다른 모든 행 닫기
$('.collapse-row').slideUp();
$('.toggle-icon').removeClass('bi-chevron-down').addClass('bi-chevron-right');
// 현재 행 열기
detailRow.slideDown();
icon.removeClass('bi-chevron-right').addClass('bi-chevron-down');
// 상세 정보 로드
loadCompoundDetailInline(compoundId);
}
});
}
// 모달 표시
$('#patientCompoundsModal').modal('show');
}
}).fail(function() {
alert('처방 내역을 불러오는데 실패했습니다.');
});
}
}).fail(function() {
alert('환자 정보를 불러오는데 실패했습니다.');
});
}
// 환자 처방 내역 모달 내에서 조제 상세 정보 로드 (인라인)
function loadCompoundDetailInline(compoundId) {
$.get(`/api/compounds/${compoundId}`, function(response) {
if (response.success && response.data) {
const data = response.data;
// 구성 약재 테이블
let ingredientsHtml = '| 약재명 | 보험코드 | 첩당용량 | 총용량 |
';
if (data.ingredients && data.ingredients.length > 0) {
data.ingredients.forEach(ing => {
ingredientsHtml += `
| ${ing.herb_name} |
${ing.insurance_code || '-'} |
${ing.grams_per_cheop}g |
${ing.total_grams}g |
`;
});
} else {
ingredientsHtml += '| 약재 정보가 없습니다 |
';
}
ingredientsHtml += '
';
$(`#ingredients-${compoundId}`).html(ingredientsHtml);
// 재고 소비 내역 테이블
let consumptionsHtml = '| 약재명 | 원산지 | 도매상 | 사용량 | 단가 | 원가 |
';
if (data.consumptions && data.consumptions.length > 0) {
let totalCost = 0;
data.consumptions.forEach(con => {
totalCost += con.cost_amount || 0;
consumptionsHtml += `
| ${con.herb_name} |
${con.origin_country || '-'} |
${con.supplier_name || '-'} |
${con.quantity_used}g |
${formatCurrency(con.unit_cost_per_g)}/g |
${formatCurrency(con.cost_amount)} |
`;
});
consumptionsHtml += `
| 총 원가: |
${formatCurrency(totalCost)} |
`;
} else {
consumptionsHtml += '| 재고 소비 내역이 없습니다 |
';
}
consumptionsHtml += '
';
$(`#consumptions-${compoundId}`).html(consumptionsHtml);
}
}).fail(function() {
$(`#ingredients-${compoundId}`).html('데이터를 불러오는데 실패했습니다.
');
$(`#consumptions-${compoundId}`).html('데이터를 불러오는데 실패했습니다.
');
});
}
// 처방 목록 로드
function loadFormulas() {
$.get('/api/formulas', function(response) {
if (response.success) {
const tbody = $('#formulasList');
tbody.empty();
response.data.forEach(formula => {
tbody.append(`
| ${formula.formula_code || '-'} |
${formula.formula_name} |
${formula.base_cheop}첩 |
${formula.base_pouches}파우치 |
|
|
`);
});
// 구성 약재 보기
$('.view-ingredients').on('click', function() {
const formulaId = $(this).data('id');
$.get(`/api/formulas/${formulaId}/ingredients`, function(response) {
if (response.success) {
let ingredientsList = response.data.map(ing =>
`${ing.herb_name}: ${ing.grams_per_cheop}g`
).join(', ');
alert('구성 약재:\n' + ingredientsList);
}
});
});
}
});
}
// 처방 구성 약재 추가 (모달)
let formulaIngredientCount = 0;
$('#addFormulaIngredientBtn').on('click', function() {
formulaIngredientCount++;
$('#formulaIngredients').append(`
|
|
|
|
|
`);
// 약재 목록 로드
const selectElement = $(`#formulaIngredients tr[data-row="${formulaIngredientCount}"] .herb-select`);
loadHerbsForSelect(selectElement);
// 삭제 버튼 이벤트
$(`#formulaIngredients tr[data-row="${formulaIngredientCount}"] .remove-ingredient`).on('click', function() {
$(this).closest('tr').remove();
});
});
// 처방 저장
$('#saveFormulaBtn').on('click', function() {
const ingredients = [];
$('#formulaIngredients tr').each(function() {
const herbId = $(this).find('.herb-select').val();
const grams = $(this).find('.grams-input').val();
if (herbId && grams) {
ingredients.push({
herb_item_id: parseInt(herbId),
grams_per_cheop: parseFloat(grams),
notes: $(this).find('.notes-input').val()
});
}
});
const formulaData = {
formula_code: $('#formulaCode').val(),
formula_name: $('#formulaName').val(),
formula_type: $('#formulaType').val(),
base_cheop: parseInt($('#baseCheop').val()),
base_pouches: parseInt($('#basePouches').val()),
description: $('#formulaDescription').val(),
ingredients: ingredients
};
$.ajax({
url: '/api/formulas',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(formulaData),
success: function(response) {
if (response.success) {
alert('처방이 등록되었습니다.');
$('#formulaModal').modal('hide');
$('#formulaForm')[0].reset();
$('#formulaIngredients').empty();
loadFormulas();
}
},
error: function(xhr) {
alert('오류: ' + xhr.responseJSON.error);
}
});
});
// 조제 관리
$('#newCompoundBtn').on('click', function() {
$('#compoundForm').show();
$('#compoundEntryForm')[0].reset();
$('#compoundIngredients').empty();
});
$('#cancelCompoundBtn').on('click', function() {
$('#compoundForm').hide();
});
// 제수 변경 시 첩수 자동 계산
$('#jeCount').on('input', function() {
const jeCount = parseFloat($(this).val()) || 0;
const cheopTotal = jeCount * 20;
const pouchTotal = jeCount * 30;
$('#cheopTotal').val(cheopTotal);
$('#pouchTotal').val(pouchTotal);
// 약재별 총 용량 재계산
updateIngredientTotals();
});
// 처방 선택 시 구성 약재 로드
$('#compoundFormula').on('change', function() {
const formulaId = $(this).val();
if (!formulaId) {
$('#compoundIngredients').empty();
return;
}
// 직접조제인 경우
if (formulaId === 'custom') {
$('#compoundIngredients').empty();
// 빈 행 하나 추가
addEmptyIngredientRow();
return;
}
// 등록된 처방인 경우
$.get(`/api/formulas/${formulaId}/ingredients`, function(response) {
if (response.success) {
$('#compoundIngredients').empty();
response.data.forEach(ing => {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const totalGrams = ing.grams_per_cheop * cheopTotal;
// 제품 선택 옵션 생성
let productOptions = '';
if (ing.available_products && ing.available_products.length > 0) {
ing.available_products.forEach(product => {
const specInfo = product.specification ? ` [${product.specification}]` : '';
productOptions += ``;
});
}
$('#compoundIngredients').append(`
|
${ing.herb_name}
${ing.total_available_stock > 0
? `(총 ${ing.total_available_stock.toFixed(0)}g 사용 가능)`
: '(재고 없음)'}
|
|
${totalGrams.toFixed(1)} |
|
|
대기중 |
|
`);
// 첫 번째 제품 자동 선택 및 원산지 로드
const tr = $(`tr[data-ingredient-code="${ing.ingredient_code}"]`);
if (ing.available_products && ing.available_products.length > 0) {
const firstProduct = ing.available_products[0];
tr.find('.product-select').val(firstProduct.herb_item_id);
tr.attr('data-herb-id', firstProduct.herb_item_id);
// 원산지/로트 옵션 로드
loadOriginOptions(firstProduct.herb_item_id, totalGrams);
}
});
// 재고 확인
checkStockForCompound();
// 제품 선택 변경 이벤트
$('.product-select').on('change', function() {
const herbId = $(this).val();
const row = $(this).closest('tr');
if (herbId) {
row.attr('data-herb-id', herbId);
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const gramsPerCheop = parseFloat(row.find('.grams-per-cheop').val()) || 0;
const totalGrams = gramsPerCheop * cheopTotal;
// 원산지/로트 옵션 로드
loadOriginOptions(herbId, totalGrams);
} else {
row.attr('data-herb-id', '');
row.find('.origin-select').empty().append('').prop('disabled', true);
row.find('.stock-status').text('대기중');
}
});
// 용량 변경 이벤트
$('.grams-per-cheop').on('input', function() {
updateIngredientTotals();
// 원산지 옵션 다시 로드
const row = $(this).closest('tr');
const herbId = row.attr('data-herb-id');
if (herbId) {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const gramsPerCheop = parseFloat($(this).val()) || 0;
const totalGrams = gramsPerCheop * cheopTotal;
loadOriginOptions(herbId, totalGrams);
}
});
// 삭제 버튼 이벤트
$('.remove-compound-ingredient').on('click', function() {
$(this).closest('tr').remove();
});
}
});
});
// 약재별 총 용량 업데이트
function updateIngredientTotals() {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
$('#compoundIngredients tr').each(function() {
const gramsPerCheop = parseFloat($(this).find('.grams-per-cheop').val()) || 0;
const totalGrams = gramsPerCheop * cheopTotal;
$(this).find('.total-grams').text(totalGrams.toFixed(1));
});
checkStockForCompound();
}
// 재고 확인
function checkStockForCompound() {
$('#compoundIngredients tr').each(function() {
const herbId = $(this).data('herb-id');
const totalGrams = parseFloat($(this).find('.total-grams').text()) || 0;
const $stockStatus = $(this).find('.stock-status');
// TODO: API 호출로 실제 재고 확인
$stockStatus.text('재고 확인 필요');
});
}
// 조제 약재 추가
// 빈 약재 행 추가 함수
function addEmptyIngredientRow() {
const newRow = $(`
|
|
|
0.0 |
|
- |
|
`);
$('#compoundIngredients').append(newRow);
// 약재 목록 로드
loadHerbsForSelect(newRow.find('.herb-select-compound'));
// 약재 선택 시 원산지 옵션 로드
newRow.find('.herb-select-compound').on('change', function() {
const herbId = $(this).val();
if (herbId) {
const row = $(this).closest('tr');
row.attr('data-herb-id', herbId);
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const gramsPerCheop = parseFloat(row.find('.grams-per-cheop').val()) || 0;
const totalGrams = gramsPerCheop * cheopTotal;
loadOriginOptions(herbId, totalGrams);
}
});
// 이벤트 바인딩
newRow.find('.grams-per-cheop').on('input', function() {
updateIngredientTotals();
// 원산지 옵션 다시 로드
const herbId = $(this).closest('tr').attr('data-herb-id');
if (herbId) {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const gramsPerCheop = parseFloat($(this).val()) || 0;
const totalGrams = gramsPerCheop * cheopTotal;
loadOriginOptions(herbId, totalGrams);
}
});
newRow.find('.remove-compound-ingredient').on('click', function() {
$(this).closest('tr').remove();
updateIngredientTotals();
});
}
$('#addIngredientBtn').on('click', function() {
addEmptyIngredientRow();
});
// 기존 약재 추가 버튼 (기존 코드 삭제)
/*
$('#addIngredientBtn').on('click', function() {
const newRow = $(`
|
|
|
0.0 |
- |
|
`);
$('#compoundIngredients').append(newRow);
// 약재 목록 로드
loadHerbsForSelect(newRow.find('.herb-select-compound'));
// 이벤트 바인딩
newRow.find('.grams-per-cheop').on('input', updateIngredientTotals);
newRow.find('.remove-compound-ingredient').on('click', function() {
$(this).closest('tr').remove();
});
newRow.find('.herb-select-compound').on('change', function() {
const herbId = $(this).val();
$(this).closest('tr').attr('data-herb-id', herbId);
updateIngredientTotals();
});
});
*/
// 조제 실행
$('#compoundEntryForm').on('submit', function(e) {
e.preventDefault();
const ingredients = [];
$('#compoundIngredients tr').each(function() {
const herbId = $(this).data('herb-id');
const gramsPerCheop = parseFloat($(this).find('.grams-per-cheop').val());
const totalGrams = parseFloat($(this).find('.total-grams').text());
const originCountry = $(this).find('.origin-select').val();
if (herbId && gramsPerCheop) {
ingredients.push({
herb_item_id: parseInt(herbId),
grams_per_cheop: gramsPerCheop,
total_grams: totalGrams,
origin_country: originCountry || null // 원산지 선택 정보 추가
});
}
});
const compoundData = {
patient_id: $('#compoundPatient').val() ? parseInt($('#compoundPatient').val()) : null,
formula_id: $('#compoundFormula').val() ? parseInt($('#compoundFormula').val()) : null,
je_count: parseFloat($('#jeCount').val()),
cheop_total: parseFloat($('#cheopTotal').val()),
pouch_total: parseFloat($('#pouchTotal').val()),
ingredients: ingredients
};
$.ajax({
url: '/api/compounds',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(compoundData),
success: function(response) {
if (response.success) {
alert(`조제가 완료되었습니다.\n원가: ${formatCurrency(response.total_cost)}`);
$('#compoundForm').hide();
loadCompounds();
}
},
error: function(xhr) {
alert('오류: ' + xhr.responseJSON.error);
}
});
});
// 조제 내역 로드
function loadCompounds() {
$.get('/api/compounds', function(response) {
const tbody = $('#compoundsList');
tbody.empty();
if (response.success && response.data.length > 0) {
// 통계 업데이트
const today = new Date().toISOString().split('T')[0];
const currentMonth = new Date().toISOString().slice(0, 7);
let todayCount = 0;
let monthCount = 0;
response.data.forEach((compound, index) => {
// 통계 계산
if (compound.compound_date === today) todayCount++;
if (compound.compound_date && compound.compound_date.startsWith(currentMonth)) monthCount++;
// 상태 뱃지
let statusBadge = '';
switch(compound.status) {
case 'PREPARED':
statusBadge = '조제완료';
break;
case 'DISPENSED':
statusBadge = '출고완료';
break;
case 'CANCELLED':
statusBadge = '취소';
break;
default:
statusBadge = '대기';
}
const row = $(`
| ${response.data.length - index} |
${compound.compound_date || ''} ${compound.created_at ? compound.created_at.split(' ')[1] : ''} |
${compound.patient_name || '직접조제'} |
${compound.patient_phone || '-'} |
${compound.formula_name || '직접조제'} |
${compound.je_count || 0} |
${compound.cheop_total || 0} |
${compound.pouch_total || 0} |
${formatCurrency(compound.cost_total || 0)} |
${formatCurrency(compound.sell_price_total || 0)} |
${statusBadge} |
${compound.prescription_no || '-'} |
|
`);
tbody.append(row);
});
// 통계 업데이트
$('#todayCompoundCount').text(todayCount);
$('#monthCompoundCount').text(monthCount);
// 상세보기 버튼 이벤트
$('.view-compound-detail').on('click', function() {
const compoundId = $(this).data('id');
viewCompoundDetail(compoundId);
});
} else {
tbody.html('| 조제 내역이 없습니다. |
');
$('#todayCompoundCount').text(0);
$('#monthCompoundCount').text(0);
}
}).fail(function() {
$('#compoundsList').html('| 데이터를 불러오는데 실패했습니다. |
');
});
}
// 조제 상세보기
function viewCompoundDetail(compoundId) {
$.get(`/api/compounds/${compoundId}`, function(response) {
if (response.success && response.data) {
const data = response.data;
// 환자 정보
$('#detailPatientName').text(data.patient_name || '직접조제');
$('#detailPatientPhone').text(data.patient_phone || '-');
$('#detailCompoundDate').text(data.compound_date || '-');
// 처방 정보
$('#detailFormulaName').text(data.formula_name || '직접조제');
$('#detailPrescriptionNo').text(data.prescription_no || '-');
$('#detailQuantities').text(`${data.je_count}제 / ${data.cheop_total}첩 / ${data.pouch_total}파우치`);
// 처방 구성 약재
const ingredientsBody = $('#detailIngredients');
ingredientsBody.empty();
if (data.ingredients && data.ingredients.length > 0) {
data.ingredients.forEach(ing => {
ingredientsBody.append(`
| ${ing.herb_name} |
${ing.insurance_code || '-'} |
${ing.grams_per_cheop}g |
${ing.total_grams}g |
${ing.notes || '-'} |
`);
});
}
// 재고 소비 내역
const consumptionsBody = $('#detailConsumptions');
consumptionsBody.empty();
if (data.consumptions && data.consumptions.length > 0) {
data.consumptions.forEach(con => {
consumptionsBody.append(`
| ${con.herb_name} |
${con.origin_country || '-'} |
${con.supplier_name || '-'} |
${con.quantity_used}g |
${formatCurrency(con.unit_cost_per_g)}/g |
${formatCurrency(con.cost_amount)} |
`);
});
}
// 총 원가
$('#detailTotalCost').text(formatCurrency(data.cost_total || 0));
// 비고
$('#detailNotes').text(data.notes || '');
// 부모 모달(환자 처방 내역)을 임시로 숨기고 조제 상세 모달 열기
const parentModal = $('#patientCompoundsModal');
const wasParentOpen = parentModal.hasClass('show');
if (wasParentOpen) {
// 부모 모달 숨기기 (DOM에서 제거하지 않음)
parentModal.modal('hide');
// 조제 상세 모달이 닫힐 때 부모 모달 다시 열기
$('#compoundDetailModal').off('hidden.bs.modal').on('hidden.bs.modal', function() {
parentModal.modal('show');
});
}
// 조제 상세 모달 열기
$('#compoundDetailModal').modal('show');
}
}).fail(function() {
alert('조제 상세 정보를 불러오는데 실패했습니다.');
});
}
// 재고 현황 로드
function loadInventory() {
$.get('/api/inventory/summary', function(response) {
if (response.success) {
const tbody = $('#inventoryList');
tbody.empty();
let totalValue = 0;
let herbsInStock = 0;
// 주성분코드 기준 보유 현황 표시
if (response.summary) {
const summary = response.summary;
const coverageHtml = `
📊 급여 약재 보유 현황
전체 급여 약재: ${summary.total_ingredient_codes || 454}개 주성분
보유 약재: ${summary.owned_ingredient_codes || 0}개 주성분
보유율:
${summary.coverage_rate || 0}%
${summary.owned_ingredient_codes || 0} / ${summary.total_ingredient_codes || 454}
※ 건강보험 급여 한약재 ${summary.total_ingredient_codes || 454}개 주성분 중 ${summary.owned_ingredient_codes || 0}개 보유
`;
// 재고 테이블 위에 통계 표시
if ($('#inventoryCoverage').length === 0) {
$('#inventoryList').parent().before(`${coverageHtml}
`);
} else {
$('#inventoryCoverage').html(coverageHtml);
}
}
response.data.forEach(item => {
// 원산지가 여러 개인 경우 표시
const originBadge = item.origin_count > 1
? `${item.origin_count}개 원산지`
: '';
// 효능 태그 표시
let efficacyTags = '';
if (item.efficacy_tags && item.efficacy_tags.length > 0) {
efficacyTags = item.efficacy_tags.map(tag =>
`${tag}`
).join('');
}
// 가격 범위 표시 (원산지가 여러 개이고 가격차가 있는 경우)
let priceDisplay = item.avg_price ? formatCurrency(item.avg_price) : '-';
if (item.origin_count > 1 && item.min_price && item.max_price && item.min_price !== item.max_price) {
priceDisplay = `${formatCurrency(item.min_price)} ~ ${formatCurrency(item.max_price)}`;
}
// 통계 업데이트
totalValue += item.total_value || 0;
if (item.total_quantity > 0) herbsInStock++;
tbody.append(`
| ${item.insurance_code || '-'} |
${item.herb_name}${originBadge}${efficacyTags} |
${item.total_quantity.toFixed(1)} |
${item.lot_count} |
${priceDisplay} |
${formatCurrency(item.total_value)} |
|
`);
});
// 통계 업데이트
$('#totalInventoryValue').text(formatCurrency(totalValue));
$('#totalHerbsInStock').text(`${herbsInStock}종`);
// 클릭 이벤트 바인딩
$('.view-inventory-detail').on('click', function(e) {
e.stopPropagation();
const herbId = $(this).data('herb-id');
showInventoryDetail(herbId);
});
// 입출고 내역 버튼 이벤트
$('.view-stock-ledger').on('click', function(e) {
e.stopPropagation();
const herbId = $(this).data('herb-id');
const herbName = $(this).data('herb-name');
viewStockLedger(herbId, herbName);
});
}
});
}
// 재고 상세 모달 표시
function showInventoryDetail(herbId) {
$.get(`/api/inventory/detail/${herbId}`, function(response) {
if (response.success) {
const data = response.data;
// 원산지별 재고 정보 HTML 생성
let originsHtml = '';
data.origins.forEach(origin => {
originsHtml += `
평균 단가:
${formatCurrency(origin.avg_price)}/g
재고 가치:
${formatCurrency(origin.total_value)}
| 로트ID |
수량 |
단가 |
입고일 |
도매상 |
`;
origin.lots.forEach(lot => {
originsHtml += `
| #${lot.lot_id} |
${lot.quantity_onhand.toFixed(1)}g |
${formatCurrency(lot.unit_price_per_g)} |
${lot.received_date} |
${lot.supplier_name || '-'} |
`;
});
originsHtml += `
`;
});
// 모달 생성 및 표시
const modalHtml = `
${data.total_origins > 1
? `
이 약재는 ${data.total_origins}개 원산지의 재고가 있습니다.
조제 시 원산지를 선택할 수 있습니다.
`
: ''}
${originsHtml}
`;
// 기존 모달 제거
$('#inventoryDetailModal').remove();
$('body').append(modalHtml);
// 모달 표시
const modal = new bootstrap.Modal(document.getElementById('inventoryDetailModal'));
modal.show();
}
});
}
// 약재 목록 로드
function loadHerbs() {
$.get('/api/herbs', function(response) {
if (response.success) {
const tbody = $('#herbsList');
tbody.empty();
response.data.forEach(herb => {
tbody.append(`
| ${herb.insurance_code || '-'} |
${herb.herb_name} |
${herb.specification || '-'} |
${herb.current_stock ? herb.current_stock.toFixed(1) + 'g' : '0g'} |
|
`);
});
}
});
}
// 입고장 목록 로드
function loadPurchaseReceipts() {
const startDate = $('#purchaseStartDate').val();
const endDate = $('#purchaseEndDate').val();
const supplierId = $('#purchaseSupplier').val();
let url = '/api/purchase-receipts?';
if (startDate) url += `start_date=${startDate}&`;
if (endDate) url += `end_date=${endDate}&`;
if (supplierId) url += `supplier_id=${supplierId}`;
$.get(url, function(response) {
if (response.success) {
const tbody = $('#purchaseReceiptsList');
tbody.empty();
if (response.data.length === 0) {
tbody.append('| 입고장이 없습니다. |
');
return;
}
response.data.forEach(receipt => {
tbody.append(`
| ${receipt.receipt_date} |
${receipt.supplier_name} |
${receipt.line_count}개 |
${receipt.total_amount ? formatCurrency(receipt.total_amount) : '-'} |
${receipt.total_quantity ? receipt.total_quantity.toLocaleString() + 'g' : '-'} |
${receipt.source_file || '-'} |
|
`);
});
// 이벤트 바인딩
$('.view-receipt').on('click', function() {
const receiptId = $(this).data('id');
viewReceiptDetail(receiptId);
});
$('.delete-receipt').on('click', function() {
const receiptId = $(this).data('id');
if (confirm('정말 이 입고장을 삭제하시겠습니까? 사용되지 않은 재고만 삭제 가능합니다.')) {
deleteReceipt(receiptId);
}
});
}
});
}
// 입고장 상세 보기
function viewReceiptDetail(receiptId) {
$.get(`/api/purchase-receipts/${receiptId}`, function(response) {
if (response.success) {
const data = response.data;
let linesHtml = '';
data.lines.forEach(line => {
linesHtml += `
| ${line.herb_name} |
${line.insurance_code || '-'} |
${line.origin_country || '-'} |
${line.quantity_g}g |
${formatCurrency(line.unit_price_per_g)} |
${formatCurrency(line.line_total)} |
${line.current_stock}g |
`;
});
const modalHtml = `
입고일: ${data.receipt_date}
공급업체: ${data.supplier_name}
총 금액: ${formatCurrency(data.total_amount)}
| 약재명 |
보험코드 |
원산지 |
수량 |
단가 |
금액 |
현재고 |
${linesHtml}
`;
// 기존 모달 제거
$('#receiptDetailModal').remove();
$('body').append(modalHtml);
$('#receiptDetailModal').modal('show');
}
});
}
// 입고장 삭제
function deleteReceipt(receiptId) {
$.ajax({
url: `/api/purchase-receipts/${receiptId}`,
method: 'DELETE',
success: function(response) {
if (response.success) {
alert(response.message);
loadPurchaseReceipts();
}
},
error: function(xhr) {
alert('오류: ' + xhr.responseJSON.error);
}
});
}
// 입고장 조회 버튼
$('#searchPurchaseBtn').on('click', function() {
loadPurchaseReceipts();
});
// 도매상 목록 로드 (셀렉트 박스용)
function loadSuppliersForSelect() {
$.get('/api/suppliers', function(response) {
if (response.success) {
const select = $('#uploadSupplier');
select.empty().append('');
response.data.forEach(supplier => {
select.append(``);
});
// 필터용 셀렉트 박스도 업데이트
const filterSelect = $('#purchaseSupplier');
filterSelect.empty().append('');
response.data.forEach(supplier => {
filterSelect.append(``);
});
}
});
}
// 도매상 등록
$('#saveSupplierBtn').on('click', function() {
const supplierData = {
name: $('#supplierName').val(),
business_no: $('#supplierBusinessNo').val(),
contact_person: $('#supplierContactPerson').val(),
phone: $('#supplierPhone').val(),
address: $('#supplierAddress').val()
};
if (!supplierData.name) {
alert('도매상명은 필수입니다.');
return;
}
$.ajax({
url: '/api/suppliers',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(supplierData),
success: function(response) {
if (response.success) {
alert('도매상이 등록되었습니다.');
$('#supplierModal').modal('hide');
$('#supplierForm')[0].reset();
loadSuppliersForSelect();
}
},
error: function(xhr) {
alert('오류: ' + xhr.responseJSON.error);
}
});
});
// 입고장 업로드
$('#purchaseUploadForm').on('submit', function(e) {
e.preventDefault();
const supplierId = $('#uploadSupplier').val();
if (!supplierId) {
alert('도매상을 선택해주세요.');
return;
}
const formData = new FormData();
const fileInput = $('#purchaseFile')[0];
if (fileInput.files.length === 0) {
alert('파일을 선택해주세요.');
return;
}
formData.append('file', fileInput.files[0]);
formData.append('supplier_id', supplierId);
$('#uploadResult').html('업로드 중...
');
$.ajax({
url: '/api/upload/purchase',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
let summaryHtml = '';
if (response.summary) {
summaryHtml = `
형식: ${response.summary.format}
처리: ${response.summary.processed_rows}개 라인
품목: ${response.summary.total_items}종
수량: ${response.summary.total_quantity}
금액: ${response.summary.total_amount}
`;
}
$('#uploadResult').html(
`
${response.message}
${summaryHtml}
`
);
$('#purchaseUploadForm')[0].reset();
// 입고장 목록 새로고침
loadPurchaseReceipts();
}
},
error: function(xhr) {
$('#uploadResult').html(
`
오류: ${xhr.responseJSON.error}
`
);
}
});
});
// 검색 기능
$('#patientSearch').on('keyup', function() {
const value = $(this).val().toLowerCase();
$('#patientsList tr').filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1);
});
});
$('#inventorySearch').on('keyup', function() {
const value = $(this).val().toLowerCase();
$('#inventoryList tr').filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1);
});
});
// 헬퍼 함수들
function loadPatientsForSelect() {
$.get('/api/patients', function(response) {
if (response.success) {
const select = $('#compoundPatient');
select.empty().append('');
response.data.forEach(patient => {
select.append(``);
});
}
});
}
function loadFormulasForSelect() {
$.get('/api/formulas', function(response) {
if (response.success) {
const select = $('#compoundFormula');
select.empty().append('');
// 직접조제 옵션 추가
select.append('');
// 등록된 처방 추가
if (response.data.length > 0) {
select.append('');
}
}
});
}
function loadHerbsForSelect(selectElement) {
$.get('/api/herbs', function(response) {
if (response.success) {
selectElement.empty().append('');
response.data.forEach(herb => {
selectElement.append(``);
});
}
});
}
// 원산지별 재고 옵션 로드
function loadOriginOptions(herbId, requiredQty) {
$.get(`/api/herbs/${herbId}/available-lots`, function(response) {
if (response.success) {
const selectElement = $(`tr[data-herb-id="${herbId}"] .origin-select`);
selectElement.empty();
const origins = response.data.origins;
if (origins.length === 0) {
selectElement.append('');
selectElement.prop('disabled', true);
$(`tr[data-herb-id="${herbId}"] .stock-status`)
.html('재고 없음');
} else {
selectElement.append('');
origins.forEach(origin => {
const stockStatus = origin.total_quantity >= requiredQty ? '' : ' (재고 부족)';
const priceInfo = `${formatCurrency(origin.min_price)}/g`;
const option = ``;
selectElement.append(option);
});
selectElement.prop('disabled', false);
// 재고 상태 업데이트
const totalAvailable = response.data.total_quantity;
const statusElement = $(`tr[data-herb-id="${herbId}"] .stock-status`);
if (totalAvailable >= requiredQty) {
statusElement.html(`충분 (${totalAvailable.toFixed(1)}g)`);
} else {
statusElement.html(`부족 (${totalAvailable.toFixed(1)}g)`);
}
}
}
});
}
// 재고 원장 보기
let currentLedgerData = []; // 원본 데이터 저장
function viewStockLedger(herbId, herbName) {
const url = herbId ? `/api/stock-ledger?herb_id=${herbId}` : '/api/stock-ledger';
$.get(url, function(response) {
if (response.success) {
// 원본 데이터 저장
currentLedgerData = response.ledger;
// 헤더 업데이트
if (herbName) {
$('#stockLedgerModal .modal-title').html(` ${herbName} 입출고 원장`);
} else {
$('#stockLedgerModal .modal-title').html(` 전체 입출고 원장`);
}
// 필터 적용하여 표시
applyLedgerFilters();
// 약재 필터 옵션 업데이트
const herbFilter = $('#ledgerHerbFilter');
if (herbFilter.find('option').length <= 1) {
response.summary.forEach(herb => {
herbFilter.append(``);
});
}
$('#stockLedgerModal').modal('show');
}
}).fail(function() {
alert('입출고 내역을 불러오는데 실패했습니다.');
});
}
// 필터 적용 함수
function applyLedgerFilters() {
const typeFilter = $('#ledgerTypeFilter').val();
const tbody = $('#stockLedgerList');
tbody.empty();
// 필터링된 데이터
let filteredData = currentLedgerData;
// 타입 필터 적용
if (typeFilter) {
filteredData = currentLedgerData.filter(entry => entry.event_type === typeFilter);
}
// 데이터 표시
filteredData.forEach(entry => {
let typeLabel = '';
let typeBadge = '';
switch(entry.event_type) {
case 'PURCHASE':
case 'RECEIPT':
typeLabel = '입고';
typeBadge = 'badge bg-success';
break;
case 'CONSUME':
typeLabel = '출고';
typeBadge = 'badge bg-danger';
break;
case 'ADJUST':
typeLabel = '보정';
typeBadge = 'badge bg-warning';
break;
default:
typeLabel = entry.event_type;
typeBadge = 'badge bg-secondary';
}
const quantity = Math.abs(entry.quantity_delta);
const sign = entry.quantity_delta > 0 ? '+' : '-';
const quantityDisplay = entry.quantity_delta > 0
? `+${quantity.toFixed(1)}g`
: `-${quantity.toFixed(1)}g`;
const referenceInfo = entry.patient_name
? `${entry.patient_name}`
: entry.supplier_name || '-';
tbody.append(`
| ${entry.event_time} |
${typeLabel} |
${entry.herb_name} |
${quantityDisplay} |
${entry.unit_cost_per_g ? formatCurrency(entry.unit_cost_per_g) + '/g' : '-'} |
${entry.origin_country || '-'} |
${referenceInfo} |
${entry.reference_no || '-'} |
`);
});
// 데이터가 없는 경우
if (filteredData.length === 0) {
tbody.append(`
| 데이터가 없습니다. |
`);
}
}
// 입출고 원장 모달 버튼 이벤트
$('#showStockLedgerBtn').on('click', function() {
viewStockLedger(null, null);
});
// 필터 변경 이벤트
$('#ledgerHerbFilter').on('change', function() {
const herbId = $(this).val();
// 약재 필터 변경 시 데이터 재로드
if (herbId) {
const herbName = $('#ledgerHerbFilter option:selected').text();
viewStockLedger(herbId, herbName);
} else {
viewStockLedger(null, null);
}
});
// 타입 필터 변경 이벤트 (현재 데이터에서 필터링만)
$('#ledgerTypeFilter').on('change', function() {
applyLedgerFilters();
});
// ==================== 재고 보정 ====================
// 재고 보정 모달 열기
$('#showStockAdjustmentBtn').on('click', function() {
// 현재 날짜 설정
$('#adjustmentDate').val(new Date().toISOString().split('T')[0]);
$('#adjustmentItemsList').empty();
$('#stockAdjustmentForm')[0].reset();
$('#stockAdjustmentModal').modal('show');
});
// 재고 보정 내역 모달 열기
$('#showAdjustmentHistoryBtn').on('click', function() {
loadAdjustmentHistory();
});
// 재고 보정 내역 로드
function loadAdjustmentHistory() {
$.get('/api/stock-adjustments', function(response) {
if (response.success) {
const tbody = $('#adjustmentHistoryList');
tbody.empty();
if (response.data.length === 0) {
tbody.append(`
| 보정 내역이 없습니다. |
`);
} else {
response.data.forEach(adj => {
// 보정 유형 한글 변환
let typeLabel = '';
switch(adj.adjustment_type) {
case 'LOSS': typeLabel = '감모/손실'; break;
case 'FOUND': typeLabel = '발견'; break;
case 'RECOUNT': typeLabel = '재고조사'; break;
case 'DAMAGE': typeLabel = '파손'; break;
case 'EXPIRE': typeLabel = '유통기한 경과'; break;
default: typeLabel = adj.adjustment_type;
}
tbody.append(`
| ${adj.adjustment_date} |
${adj.adjustment_no} |
${typeLabel} |
${adj.detail_count || 0}개 |
${adj.created_by || '-'} |
${adj.notes || '-'} |
|
`);
});
// 상세보기 버튼 이벤트
$('.view-adjustment-detail').on('click', function() {
const adjustmentId = $(this).data('id');
viewAdjustmentDetail(adjustmentId);
});
}
$('#adjustmentHistoryModal').modal('show');
}
}).fail(function() {
alert('보정 내역을 불러오는데 실패했습니다.');
});
}
// 재고 보정 상세 조회
function viewAdjustmentDetail(adjustmentId) {
$.get(`/api/stock-adjustments/${adjustmentId}`, function(response) {
if (response.success) {
const data = response.data;
// 보정 정보 표시
$('#detailAdjustmentNo').text(data.adjustment_no);
$('#detailAdjustmentDate').text(data.adjustment_date);
// 보정 유형 한글 변환
let typeLabel = '';
switch(data.adjustment_type) {
case 'LOSS': typeLabel = '감모/손실'; break;
case 'FOUND': typeLabel = '발견'; break;
case 'RECOUNT': typeLabel = '재고조사'; break;
case 'DAMAGE': typeLabel = '파손'; break;
case 'EXPIRE': typeLabel = '유통기한 경과'; break;
default: typeLabel = data.adjustment_type;
}
$('#detailAdjustmentType').html(`${typeLabel}`);
$('#detailAdjustmentCreatedBy').text(data.created_by || '-');
$('#detailAdjustmentNotes').text(data.notes || '-');
// 보정 상세 항목 표시
const itemsBody = $('#detailAdjustmentItems');
itemsBody.empty();
if (data.details && data.details.length > 0) {
data.details.forEach(item => {
const delta = item.quantity_delta;
let deltaHtml = '';
if (delta > 0) {
deltaHtml = `+${delta.toFixed(1)}g`;
} else if (delta < 0) {
deltaHtml = `${delta.toFixed(1)}g`;
} else {
deltaHtml = '0g';
}
itemsBody.append(`
| ${item.herb_name} |
${item.insurance_code || '-'} |
${item.origin_country || '-'} |
#${item.lot_id} |
${item.quantity_before.toFixed(1)}g |
${item.quantity_after.toFixed(1)}g |
${deltaHtml} |
${item.reason || '-'} |
`);
});
}
// 보정 상세 모달 표시
$('#adjustmentDetailModal').modal('show');
}
}).fail(function() {
alert('보정 상세 정보를 불러오는데 실패했습니다.');
});
}
// 보정 대상 약재 추가
let adjustmentItemCount = 0;
$('#addAdjustmentItemBtn').on('click', function() {
addAdjustmentItemRow();
});
function addAdjustmentItemRow() {
adjustmentItemCount++;
const rowId = `adj-item-${adjustmentItemCount}`;
const newRow = $(`
|
|
|
- |
|
- |
|
|
`);
$('#adjustmentItemsList').append(newRow);
// 약재 목록 로드
loadHerbsForSelect(newRow.find('.adj-herb-select'));
// 약재 선택 이벤트
newRow.find('.adj-herb-select').on('change', function() {
const herbId = $(this).val();
const row = $(this).closest('tr');
if (herbId) {
loadLotsForAdjustment(herbId, row);
} else {
row.find('.adj-lot-select').empty().append('').prop('disabled', true);
row.find('.before-qty').text('-');
row.find('.after-qty-input').val('');
row.find('.delta-qty').text('-');
}
});
// 로트 선택 이벤트
newRow.find('.adj-lot-select').on('change', function() {
const selectedOption = $(this).find('option:selected');
const row = $(this).closest('tr');
if (selectedOption.val()) {
const beforeQty = parseFloat(selectedOption.data('qty')) || 0;
row.find('.before-qty').text(beforeQty.toFixed(1) + 'g');
row.data('before-qty', beforeQty);
// 기존 변경후 값이 있으면 델타 재계산
const afterQty = parseFloat(row.find('.after-qty-input').val());
if (!isNaN(afterQty)) {
updateDelta(row, beforeQty, afterQty);
}
} else {
row.find('.before-qty').text('-');
row.find('.after-qty-input').val('');
row.find('.delta-qty').text('-');
}
});
// 변경후 수량 입력 이벤트
newRow.find('.after-qty-input').on('input', function() {
const row = $(this).closest('tr');
const beforeQty = row.data('before-qty') || 0;
const afterQty = parseFloat($(this).val()) || 0;
updateDelta(row, beforeQty, afterQty);
});
// 삭제 버튼
newRow.find('.remove-adj-item').on('click', function() {
$(this).closest('tr').remove();
});
}
// 약재별 로트 목록 로드
function loadLotsForAdjustment(herbId, row) {
$.get(`/api/inventory/detail/${herbId}`, function(response) {
if (response.success) {
const lotSelect = row.find('.adj-lot-select');
lotSelect.empty();
lotSelect.append('');
const data = response.data;
// 원산지별로 로트 표시
data.origins.forEach(origin => {
const optgroup = $(`