feat: 조제 화면에 2단계 선택 시스템 구현

- 1단계: 제품 선택 (같은 주성분의 여러 제품 중 선택)
- 2단계: 원산지/로트 선택 (선택한 제품의 원산지별 로트 중 선택)
- formula_ingredients API 응답 구조 변경에 따른 프론트엔드 수정
  - available_products 배열 기반 렌더링
  - 제품 선택 드롭다운과 원산지 선택 드롭다운 분리
  - 제품 변경 시 원산지/로트 옵션 동적 로드

예시:
- 인삼 → 신흥인삼 선택 → [한국산|중국산] 선택 가능
- 인삼 → 세화인삼 선택 → [한국산|미국산] 선택 가능
This commit is contained in:
시골약사 2026-02-15 17:46:04 +00:00
parent 9fa1f7a031
commit 55974423ea

View File

@ -595,20 +595,39 @@ $(document).ready(function() {
const cheopTotal = parseFloat($('#cheopTotal').val()) || 0;
const totalGrams = ing.grams_per_cheop * cheopTotal;
// 제품 선택 옵션 생성
let productOptions = '<option value="">제품 선택</option>';
if (ing.available_products && ing.available_products.length > 0) {
ing.available_products.forEach(product => {
const specInfo = product.specification ? ` [${product.specification}]` : '';
productOptions += `<option value="${product.herb_item_id}">${product.herb_name}${specInfo} (재고: ${product.stock.toFixed(0)}g)</option>`;
});
}
$('#compoundIngredients').append(`
<tr data-herb-id="${ing.herb_item_id}">
<td>${ing.herb_name}</td>
<tr data-ingredient-code="${ing.ingredient_code}" data-herb-id="">
<td>
${ing.herb_name}
${ing.total_available_stock > 0
? `<small class="text-success">(총 ${ing.total_available_stock.toFixed(0)}g 사용 가능)</small>`
: '<small class="text-danger">(재고 없음)</small>'}
</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="origin-select-cell">
<select class="form-control form-control-sm origin-select" disabled>
<option value="">로딩중...</option>
<td class="product-select-cell">
<select class="form-control form-control-sm product-select" ${ing.available_products.length === 0 ? 'disabled' : ''}>
${productOptions}
</select>
</td>
<td class="stock-status">확인중...</td>
<td class="origin-select-cell">
<select class="form-control form-control-sm origin-select" disabled>
<option value="">제품 먼저 선택</option>
</select>
</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>
@ -617,15 +636,54 @@ $(document).ready(function() {
</tr>
`);
// 각 약재별로 원산지별 재고 확인
loadOriginOptions(ing.herb_item_id, totalGrams);
// 첫 번째 제품 자동 선택 및 원산지 로드
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('<option value="">제품 먼저 선택</option>').prop('disabled', true);
row.find('.stock-status').text('대기중');
}
});
// 용량 변경 이벤트
$('.grams-per-cheop').on('input', updateIngredientTotals);
$('.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() {