diff --git a/static/medicine_master.js b/static/medicine_master.js
new file mode 100644
index 0000000..2b2fd0c
--- /dev/null
+++ b/static/medicine_master.js
@@ -0,0 +1,450 @@
+/**
+ * 의약품 마스터 검색 + 입고 장바구니 모듈
+ */
+(function() {
+ 'use strict';
+
+ // 장바구니 데이터 (세션 메모리)
+ let cart = [];
+
+ const ORIGIN_OPTIONS = ['한국','중국','베트남','인도','태국','페루','일본','기타'];
+
+ // ─── 검색 ───────────────────────────────────────────────
+
+ function searchMedicine() {
+ const query = $('#medSearchInput').val().trim();
+ const category = $('#medCategoryFilter').val();
+ const packageType = $('#medPackageFilter').val();
+
+ if (query.length < 2) {
+ alert('검색어는 2자 이상 입력하세요.');
+ return;
+ }
+
+ const params = new URLSearchParams({ q: query, limit: 100 });
+ if (category) params.append('category', category);
+ if (packageType) params.append('package_type', packageType);
+
+ $('#medSearchResults').html('
| 검색중... |
');
+
+ $.get(`/api/medicine-master/search?${params}`, function(response) {
+ if (!response.success) {
+ $('#medSearchResults').html(`| ${response.error} |
`);
+ return;
+ }
+
+ const tbody = $('#medSearchResults');
+ tbody.empty();
+ $('#medResultCount').text(response.count);
+
+ if (response.data.length === 0) {
+ tbody.html('| 검색 결과가 없습니다. |
');
+ return;
+ }
+
+ response.data.forEach(item => {
+ let categoryBadge = '';
+ switch(item.category) {
+ case '일반의약품':
+ categoryBadge = '일반';
+ break;
+ case '전문의약품':
+ categoryBadge = '전문';
+ break;
+ case '한약재':
+ categoryBadge = '한약재';
+ break;
+ case '원료의약품':
+ categoryBadge = '원료';
+ break;
+ default:
+ categoryBadge = `${item.category || '-'}`;
+ }
+
+ const cleanName = item.product_name.replace(/ /g, ' ').trim();
+ const notes = item.notes ? item.notes.replace(/ /g, ' ').trim() : '';
+ const inCart = cart.some(c => c.standard_code === item.standard_code);
+ const itemJson = JSON.stringify(item).replace(/'/g, "'");
+
+ tbody.append(`
+
+
+ ${cleanName}
+ ${item.form_type ? ` ${item.form_type}` : ''}
+ |
+ ${item.company_name || '-'} |
+ ${item.spec || '-'} |
+ ${item.package_type || '-'} |
+ ${categoryBadge} |
+ ${item.standard_code || '-'} |
+ ${notes.length > 30 ? notes.substring(0, 30) + '...' : notes} |
+
+
+
+ |
+
+ `);
+ });
+
+ // 상세보기 이벤트
+ $('.med-detail-btn').off('click').on('click', function() {
+ const item = JSON.parse($(this).attr('data-item'));
+ showMedicineDetail(item);
+ });
+
+ // 장바구니 담기 이벤트
+ $('.med-add-cart-btn').off('click').on('click', function() {
+ const item = JSON.parse($(this).attr('data-item'));
+ addToCart(item);
+ $(this).prop('disabled', true);
+ });
+
+ }).fail(function(xhr) {
+ $('#medSearchResults').html(`| 검색 실패: ${xhr.responseJSON?.error || '서버 오류'} |
`);
+ });
+ }
+
+ // ─── 상세보기 모달 ─────────────────────────────────────
+
+ function showMedicineDetail(item) {
+ const cleanName = item.product_name.replace(/ /g, ' ').trim();
+ $('#medDetailTitle').text(cleanName);
+
+ const fields = [
+ { label: '상품명', value: cleanName },
+ { label: '업체명', value: item.company_name },
+ { label: '규격', value: item.spec },
+ { label: '제형구분', value: item.form_type },
+ { label: '포장형태', value: item.package_type },
+ { label: '전문일반구분', value: item.category },
+ { label: '품목기준코드', value: item.item_std_code },
+ { label: '대표코드', value: item.representative_code },
+ { label: '표준코드', value: item.standard_code },
+ { label: '일반명코드', value: item.ingredient_name_code },
+ { label: 'ATC코드', value: item.atc_code },
+ { label: '비고', value: item.notes?.replace(/ /g, ' ') },
+ ];
+
+ let html = '';
+ fields.forEach(f => {
+ if (f.value) {
+ html += `| ${f.label} | ${f.value} |
`;
+ }
+ });
+ html += '
';
+
+ $('#medDetailBody').html(html);
+ $('#medDetailModal').modal('show');
+ }
+
+ // ─── 규격 파싱 ───────────────────────────────────────────
+
+ // 규격 문자열에서 그램 수 추출 (예: "500그램"→500, "1000그램"→1000)
+ function parseSpecToGrams(spec) {
+ if (!spec) return 0;
+ const s = spec.trim();
+
+ // "500그램", "1000그램", "1252.5그램"
+ let m = s.match(/^([\d.]+)\s*그램$/);
+ if (m) return parseFloat(m[1]);
+
+ // "500g", "1000G"
+ m = s.match(/^([\d.]+)\s*[gG]$/);
+ if (m) return parseFloat(m[1]);
+
+ // "1kg", "1.5Kg", "1킬로그램"
+ m = s.match(/^([\d.]+)\s*(kg|킬로그램)$/i);
+ if (m) return parseFloat(m[1]) * 1000;
+
+ // "500밀리그램" → 0.5g
+ m = s.match(/^([\d.]+)\s*밀리그램$/);
+ if (m) return parseFloat(m[1]) / 1000;
+
+ // "500mg"
+ m = s.match(/^([\d.]+)\s*mg$/i);
+ if (m) return parseFloat(m[1]) / 1000;
+
+ return 0; // 파싱 불가 ("없음" 등)
+ }
+
+ // ─── 장바구니 기능 ─────────────────────────────────────
+
+ function addToCart(item) {
+ // 중복 체크
+ if (cart.some(c => c.standard_code === item.standard_code)) {
+ alert('이미 장바구니에 있는 항목입니다.');
+ return;
+ }
+
+ const cleanName = item.product_name.replace(/ /g, ' ').trim();
+ const specGrams = parseSpecToGrams(item.spec);
+
+ cart.push({
+ standard_code: item.standard_code,
+ product_name: cleanName,
+ company_name: item.company_name || '',
+ spec: item.spec || '',
+ spec_grams: specGrams, // 규격에서 파싱한 g 수
+ qty: 1, // 수량 (포장 단위)
+ unit_price: 0, // 단가 (종이 입고장 가격)
+ origin_country: '한국',
+ _raw: item
+ });
+
+ renderCart();
+ }
+
+ function removeFromCart(index) {
+ const removed = cart.splice(index, 1)[0];
+ if (removed) {
+ $(`.med-add-cart-btn[data-code="${removed.standard_code}"]`).prop('disabled', false);
+ }
+ renderCart();
+ }
+
+ function clearCart() {
+ if (cart.length === 0) return;
+ if (!confirm('장바구니를 비우시겠습니까?')) return;
+
+ const codes = cart.map(c => c.standard_code);
+ cart = [];
+ codes.forEach(code => {
+ $(`.med-add-cart-btn[data-code="${code}"]`).prop('disabled', false);
+ });
+ renderCart();
+ }
+
+ function renderCart() {
+ const panel = $('#cartPanel');
+ const tbody = $('#cartBody');
+
+ if (cart.length === 0) {
+ panel.hide();
+ return;
+ }
+
+ panel.show();
+ $('#cartCount').text(cart.length);
+ tbody.empty();
+
+ let totalQty = 0;
+ let totalAmt = 0;
+
+ cart.forEach((item, idx) => {
+ const amt = (item.qty || 0) * (item.unit_price || 0);
+ const pricePerG = (item.spec_grams && item.unit_price)
+ ? (item.unit_price / item.spec_grams) : 0;
+ totalQty += (item.qty || 0);
+ totalAmt += amt;
+
+ const originOptions = ORIGIN_OPTIONS.map(o =>
+ ``
+ ).join('');
+
+ const specDisplay = item.spec_grams
+ ? `${item.spec}
${item.spec_grams.toLocaleString()}g`
+ : `${item.spec || '-'}
수동입력`;
+
+ tbody.append(`
+
+ | ${item.product_name} |
+ ${item.company_name || '-'} |
+ ${specDisplay} |
+
+
+ |
+
+
+ |
+
+ ${amt ? amt.toLocaleString() : '-'}
+ |
+
+ ${pricePerG ? pricePerG.toFixed(1) : '-'}
+ |
+
+
+ |
+
+
+ |
+
+ `);
+ });
+
+ $('#cartTotalQty').text(totalQty ? totalQty.toLocaleString() : '0');
+ $('#cartTotalAmt').text(totalAmt ? totalAmt.toLocaleString() + '원' : '0');
+
+ // 이벤트 바인딩
+ tbody.find('.cart-qty').off('input').on('input', function() {
+ const idx = $(this).data('idx');
+ cart[idx].qty = parseInt($(this).val()) || 0;
+ updateCartRow(idx);
+ });
+
+ tbody.find('.cart-unit-price').off('input').on('input', function() {
+ const idx = $(this).data('idx');
+ cart[idx].unit_price = parseFloat($(this).val()) || 0;
+ updateCartRow(idx);
+ });
+
+ tbody.find('.cart-origin').off('change').on('change', function() {
+ const idx = $(this).data('idx');
+ cart[idx].origin_country = $(this).val();
+ });
+
+ tbody.find('.cart-remove-btn').off('click').on('click', function() {
+ removeFromCart($(this).data('idx'));
+ });
+ }
+
+ function updateCartRow(idx) {
+ const item = cart[idx];
+ const amt = (item.qty || 0) * (item.unit_price || 0);
+ const pricePerG = (item.spec_grams && item.unit_price)
+ ? (item.unit_price / item.spec_grams) : 0;
+
+ $(`.cart-amt[data-idx="${idx}"]`).text(amt ? amt.toLocaleString() : '-');
+ $(`.cart-ppg[data-idx="${idx}"]`).html(`${pricePerG ? pricePerG.toFixed(1) : '-'}`);
+ updateCartTotals();
+ }
+
+ function updateCartTotals() {
+ let totalQty = 0;
+ let totalAmt = 0;
+ cart.forEach(item => {
+ totalQty += (item.qty || 0);
+ totalAmt += (item.qty || 0) * (item.unit_price || 0);
+ });
+ $('#cartTotalQty').text(totalQty ? totalQty.toLocaleString() : '0');
+ $('#cartTotalAmt').text(totalAmt ? totalAmt.toLocaleString() + '원' : '0');
+ }
+
+ // ─── 입고장 생성 ───────────────────────────────────────
+
+ function createReceipt() {
+ // 검증
+ const supplierId = $('#cartSupplier').val();
+ const receiptDate = $('#cartReceiptDate').val();
+ const notes = $('#cartNotes').val().trim();
+
+ if (!supplierId) {
+ alert('도매상을 선택해주세요.');
+ $('#cartSupplier').focus();
+ return;
+ }
+ if (!receiptDate) {
+ alert('입고일을 입력해주세요.');
+ $('#cartReceiptDate').focus();
+ return;
+ }
+ if (cart.length === 0) {
+ alert('장바구니가 비어있습니다.');
+ return;
+ }
+
+ // 단가 미입력 체크
+ const incomplete = cart.filter(c => !c.unit_price);
+ if (incomplete.length > 0) {
+ alert(`단가가 입력되지 않은 항목이 ${incomplete.length}건 있습니다.\n모든 항목의 단가를 입력해주세요.`);
+ return;
+ }
+
+ // 규격 미파싱 경고
+ const noSpec = cart.filter(c => !c.spec_grams);
+ if (noSpec.length > 0) {
+ alert(`규격(g)을 파싱할 수 없는 항목이 ${noSpec.length}건 있습니다.\n해당 항목은 g당단가가 계산되지 않습니다.\n\n${noSpec.map(c => '- ' + c.product_name).join('\n')}`);
+ }
+
+ // 요약 표시
+ const supplierName = $('#cartSupplier option:selected').text();
+ let totalAmt = 0;
+ cart.forEach(item => {
+ totalAmt += (item.qty || 0) * (item.unit_price || 0);
+ });
+
+ const summary = [
+ `[입고장 요약]`,
+ `도매상: ${supplierName}`,
+ `입고일: ${receiptDate}`,
+ `품목 수: ${cart.length}건`,
+ `총 금액: ${totalAmt.toLocaleString()}원`,
+ notes ? `비고: ${notes}` : '',
+ '',
+ '--- 품목 ---',
+ ...cart.map((c, i) => {
+ const amt = c.qty * c.unit_price;
+ const ppg = c.spec_grams ? (c.unit_price / c.spec_grams).toFixed(1) : '?';
+ const totalG = c.spec_grams ? (c.spec_grams * c.qty).toLocaleString() + 'g' : '?';
+ return `${i+1}. ${c.product_name} (${c.spec}) ×${c.qty} @${c.unit_price.toLocaleString()}원 = ${amt.toLocaleString()}원 [${totalG}, ${ppg}원/g]`;
+ }),
+ '',
+ '(DB 연동은 추후 구현 예정입니다)'
+ ].filter(Boolean).join('\n');
+
+ alert(summary);
+ }
+
+ // ─── 도매상 로드 ───────────────────────────────────────
+
+ function loadSuppliers() {
+ $.get('/api/suppliers', function(response) {
+ if (!response.success) return;
+ const sel = $('#cartSupplier');
+ sel.find('option:not(:first)').remove();
+ response.data.forEach(s => {
+ sel.append(``);
+ });
+ });
+ }
+
+ // ─── 초기화 ────────────────────────────────────────────
+
+ function init() {
+ // 검색
+ $('#medSearchBtn').off('click').on('click', searchMedicine);
+ $('#medSearchInput').off('keypress').on('keypress', function(e) {
+ if (e.which === 13) searchMedicine();
+ });
+ $('#medCategoryFilter, #medPackageFilter').off('change').on('change', function() {
+ if ($('#medSearchInput').val().trim().length >= 2) {
+ searchMedicine();
+ }
+ });
+
+ // 장바구니
+ $('#cartClearBtn').off('click').on('click', clearCart);
+ $('#createReceiptBtn').off('click').on('click', createReceipt);
+
+ // 입고일 기본값: 오늘
+ $('#cartReceiptDate').val(new Date().toISOString().split('T')[0]);
+
+ // 도매상 목록 로드
+ loadSuppliers();
+
+ // 이전 장바구니 상태 복원 (없으면 숨김)
+ renderCart();
+ }
+
+ // 글로벌 로드 함수 등록
+ window.loadMedicineMaster = function() {
+ init();
+ };
+})();
diff --git a/templates/medicine_master.html b/templates/medicine_master.html
new file mode 100644
index 0000000..904f7de
--- /dev/null
+++ b/templates/medicine_master.html
@@ -0,0 +1,163 @@
+
+
+
의약품 마스터 검색
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 상품명 |
+ 업체명 |
+ 규격 |
+ 포장 |
+ 분류 |
+ 표준코드 |
+ 비고 |
+ 작업 |
+
+
+
+
+ |
+
+ 검색어를 입력하세요 (2자 이상)
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 상품명 |
+ 업체 |
+ 규격 |
+ 수량 |
+ 단가(원) |
+ 금액 |
+ g당단가 |
+ 원산지 |
+ |
+
+
+
+
+
+ | 합계 |
+ 0 |
+ |
+ 0 |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+