✨ 새로운 기능 - 입고장 목록 조회 (날짜/공급업체 필터링) - 입고장 상세 보기 (모달 팝업) - 입고장 삭제 (재고 미사용시만 가능) - 입고장 라인별 수정 API 📊 화면 구성 1. 입고장 목록 테이블 - 입고일, 공급업체, 품목수, 총수량, 총금액 - 상세보기, 삭제 버튼 2. 입고장 필터링 - 시작일/종료일 선택 - 공급업체별 조회 🔧 백엔드 API - GET /api/purchase-receipts - 입고장 목록 - GET /api/purchase-receipts/<id> - 입고장 상세 - PUT /api/purchase-receipts/<id>/lines/<line_id> - 라인 수정 - DELETE /api/purchase-receipts/<id> - 입고장 삭제 🛡️ 안전장치 - 이미 조제에 사용된 재고는 수정/삭제 불가 - 재고 원장에 모든 변동사항 기록 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
760 lines
28 KiB
JavaScript
760 lines
28 KiB
JavaScript
// 한약 재고관리 시스템 - 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();
|
|
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));
|
|
}
|
|
});
|
|
|
|
// TODO: 오늘 조제 수, 최근 조제 내역
|
|
}
|
|
|
|
// 환자 목록 로드
|
|
function loadPatients() {
|
|
$.get('/api/patients', function(response) {
|
|
if (response.success) {
|
|
const tbody = $('#patientsList');
|
|
tbody.empty();
|
|
|
|
response.data.forEach(patient => {
|
|
tbody.append(`
|
|
<tr>
|
|
<td>${patient.name}</td>
|
|
<td>${patient.phone}</td>
|
|
<td>${patient.gender === 'M' ? '남' : patient.gender === 'F' ? '여' : '-'}</td>
|
|
<td>${patient.birth_date || '-'}</td>
|
|
<td>${patient.notes || '-'}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// 환자 등록
|
|
$('#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 loadFormulas() {
|
|
$.get('/api/formulas', function(response) {
|
|
if (response.success) {
|
|
const tbody = $('#formulasList');
|
|
tbody.empty();
|
|
|
|
response.data.forEach(formula => {
|
|
tbody.append(`
|
|
<tr>
|
|
<td>${formula.formula_code || '-'}</td>
|
|
<td>${formula.formula_name}</td>
|
|
<td>${formula.base_cheop}첩</td>
|
|
<td>${formula.base_pouches}파우치</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-info view-ingredients"
|
|
data-id="${formula.formula_id}">
|
|
<i class="bi bi-eye"></i> 보기
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
|
|
// 구성 약재 보기
|
|
$('.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(`
|
|
<tr data-row="${formulaIngredientCount}">
|
|
<td>
|
|
<select class="form-control form-control-sm herb-select">
|
|
<option value="">약재 선택</option>
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<input type="number" class="form-control form-control-sm grams-input"
|
|
min="0.1" step="0.1" placeholder="0.0">
|
|
</td>
|
|
<td>
|
|
<input type="text" class="form-control form-control-sm notes-input">
|
|
</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-ingredient">
|
|
<i class="bi bi-x"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
|
|
// 약재 목록 로드
|
|
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;
|
|
}
|
|
|
|
$.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;
|
|
|
|
$('#compoundIngredients').append(`
|
|
<tr data-herb-id="${ing.herb_item_id}">
|
|
<td>${ing.herb_name}</td>
|
|
<td>
|
|
<input type="number" class="form-control form-control-sm grams-per-cheop"
|
|
value="${ing.grams_per_cheop}" min="0.1" step="0.1">
|
|
</td>
|
|
<td class="total-grams">${totalGrams.toFixed(1)}</td>
|
|
<td class="stock-status">확인중...</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-compound-ingredient">
|
|
<i class="bi bi-x"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
|
|
// 재고 확인
|
|
checkStockForCompound();
|
|
|
|
// 용량 변경 이벤트
|
|
$('.grams-per-cheop').on('input', updateIngredientTotals);
|
|
|
|
// 삭제 버튼 이벤트
|
|
$('.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('재고 확인 필요');
|
|
});
|
|
}
|
|
|
|
// 조제 약재 추가
|
|
$('#addIngredientBtn').on('click', function() {
|
|
const newRow = $(`
|
|
<tr>
|
|
<td>
|
|
<select class="form-control form-control-sm herb-select-compound">
|
|
<option value="">약재 선택</option>
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<input type="number" class="form-control form-control-sm grams-per-cheop"
|
|
min="0.1" step="0.1" placeholder="0.0">
|
|
</td>
|
|
<td class="total-grams">0.0</td>
|
|
<td class="stock-status">-</td>
|
|
<td>
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-compound-ingredient">
|
|
<i class="bi bi-x"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
|
|
$('#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());
|
|
|
|
if (herbId && gramsPerCheop) {
|
|
ingredients.push({
|
|
herb_item_id: parseInt(herbId),
|
|
grams_per_cheop: gramsPerCheop,
|
|
total_grams: totalGrams
|
|
});
|
|
}
|
|
});
|
|
|
|
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() {
|
|
// TODO: 조제 내역 API 구현 필요
|
|
$('#compoundsList').html('<tr><td colspan="7" class="text-center">조제 내역이 없습니다.</td></tr>');
|
|
}
|
|
|
|
// 재고 현황 로드
|
|
function loadInventory() {
|
|
$.get('/api/inventory/summary', function(response) {
|
|
if (response.success) {
|
|
const tbody = $('#inventoryList');
|
|
tbody.empty();
|
|
|
|
response.data.forEach(item => {
|
|
tbody.append(`
|
|
<tr>
|
|
<td>${item.insurance_code || '-'}</td>
|
|
<td>${item.herb_name}</td>
|
|
<td>${item.total_quantity.toFixed(1)}</td>
|
|
<td>${item.lot_count}</td>
|
|
<td>${item.avg_price ? formatCurrency(item.avg_price) : '-'}</td>
|
|
<td>${formatCurrency(item.total_value)}</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// 약재 목록 로드
|
|
function loadHerbs() {
|
|
$.get('/api/herbs', function(response) {
|
|
if (response.success) {
|
|
const tbody = $('#herbsList');
|
|
tbody.empty();
|
|
|
|
response.data.forEach(herb => {
|
|
tbody.append(`
|
|
<tr>
|
|
<td>${herb.insurance_code || '-'}</td>
|
|
<td>${herb.herb_name}</td>
|
|
<td>${herb.specification || '-'}</td>
|
|
<td>${herb.current_stock ? herb.current_stock.toFixed(1) + 'g' : '0g'}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// 입고장 목록 로드
|
|
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('<tr><td colspan="7" class="text-center">입고장이 없습니다.</td></tr>');
|
|
return;
|
|
}
|
|
|
|
response.data.forEach(receipt => {
|
|
tbody.append(`
|
|
<tr>
|
|
<td>${receipt.receipt_date}</td>
|
|
<td>${receipt.supplier_name}</td>
|
|
<td>${receipt.line_count}개</td>
|
|
<td>${receipt.total_quantity ? receipt.total_quantity.toLocaleString() + 'g' : '-'}</td>
|
|
<td>${receipt.total_amount ? formatCurrency(receipt.total_amount) : '-'}</td>
|
|
<td>${receipt.source_file || '-'}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-info view-receipt" data-id="${receipt.receipt_id}">
|
|
<i class="bi bi-eye"></i> 상세
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger delete-receipt" data-id="${receipt.receipt_id}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
});
|
|
|
|
// 이벤트 바인딩
|
|
$('.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 += `
|
|
<tr>
|
|
<td>${line.herb_name}</td>
|
|
<td>${line.insurance_code || '-'}</td>
|
|
<td>${line.origin_country || '-'}</td>
|
|
<td>${line.quantity_g}g</td>
|
|
<td>${formatCurrency(line.unit_price_per_g)}</td>
|
|
<td>${formatCurrency(line.line_total)}</td>
|
|
<td>${line.current_stock}g</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
const modalHtml = `
|
|
<div class="modal fade" id="receiptDetailModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">입고장 상세</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<strong>입고일:</strong> ${data.receipt_date}<br>
|
|
<strong>공급업체:</strong> ${data.supplier_name}<br>
|
|
<strong>총 금액:</strong> ${formatCurrency(data.total_amount)}
|
|
</div>
|
|
<table class="table table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>약재명</th>
|
|
<th>보험코드</th>
|
|
<th>원산지</th>
|
|
<th>수량</th>
|
|
<th>단가</th>
|
|
<th>금액</th>
|
|
<th>현재고</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${linesHtml}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 기존 모달 제거
|
|
$('#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();
|
|
});
|
|
|
|
// 입고장 업로드
|
|
$('#purchaseUploadForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData();
|
|
const fileInput = $('#purchaseFile')[0];
|
|
|
|
if (fileInput.files.length === 0) {
|
|
alert('파일을 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
formData.append('file', fileInput.files[0]);
|
|
|
|
$('#uploadResult').html('<div class="alert alert-info">업로드 중...</div>');
|
|
|
|
$.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 = `<br>
|
|
<small>
|
|
형식: ${response.summary.format}<br>
|
|
처리: ${response.summary.processed_rows}개 라인<br>
|
|
품목: ${response.summary.total_items}종<br>
|
|
수량: ${response.summary.total_quantity}<br>
|
|
금액: ${response.summary.total_amount}
|
|
</small>`;
|
|
}
|
|
|
|
$('#uploadResult').html(
|
|
`<div class="alert alert-success">
|
|
<i class="bi bi-check-circle"></i> ${response.message}
|
|
${summaryHtml}
|
|
</div>`
|
|
);
|
|
$('#purchaseUploadForm')[0].reset();
|
|
|
|
// 입고장 목록 새로고침
|
|
loadPurchaseReceipts();
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
$('#uploadResult').html(
|
|
`<div class="alert alert-danger">
|
|
<i class="bi bi-x-circle"></i> 오류: ${xhr.responseJSON.error}
|
|
</div>`
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// 검색 기능
|
|
$('#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('<option value="">환자를 선택하세요</option>');
|
|
|
|
response.data.forEach(patient => {
|
|
select.append(`<option value="${patient.patient_id}">${patient.name} (${patient.phone})</option>`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadFormulasForSelect() {
|
|
$.get('/api/formulas', function(response) {
|
|
if (response.success) {
|
|
const select = $('#compoundFormula');
|
|
select.empty().append('<option value="">처방을 선택하세요</option>');
|
|
|
|
response.data.forEach(formula => {
|
|
select.append(`<option value="${formula.formula_id}">${formula.formula_name}</option>`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadHerbsForSelect(selectElement) {
|
|
$.get('/api/herbs', function(response) {
|
|
if (response.success) {
|
|
selectElement.empty().append('<option value="">약재 선택</option>');
|
|
|
|
response.data.forEach(herb => {
|
|
selectElement.append(`<option value="${herb.herb_item_id}">${herb.herb_name}</option>`);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function formatCurrency(amount) {
|
|
if (amount === null || amount === undefined) return '0원';
|
|
return new Intl.NumberFormat('ko-KR', {
|
|
style: 'currency',
|
|
currency: 'KRW'
|
|
}).format(amount);
|
|
}
|
|
}); |