feat: 의약품 마스터 입고 장바구니 UI 구현
검색 결과에서 제품을 장바구니에 담고, 종이 입고장 기준으로 수량/단가 입력 시 g당단가·금액을 자동 계산하는 프론트엔드 플로우. DB 연동은 추후 구현 예정. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c0d55f8e16
commit
9dd1f41bbb
450
static/medicine_master.js
Normal file
450
static/medicine_master.js
Normal file
@ -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('<tr><td colspan="8" class="text-center py-4"><div class="spinner-border spinner-border-sm"></div> 검색중...</td></tr>');
|
||||
|
||||
$.get(`/api/medicine-master/search?${params}`, function(response) {
|
||||
if (!response.success) {
|
||||
$('#medSearchResults').html(`<tr><td colspan="8" class="text-center text-danger py-4">${response.error}</td></tr>`);
|
||||
return;
|
||||
}
|
||||
|
||||
const tbody = $('#medSearchResults');
|
||||
tbody.empty();
|
||||
$('#medResultCount').text(response.count);
|
||||
|
||||
if (response.data.length === 0) {
|
||||
tbody.html('<tr><td colspan="8" class="text-center text-muted py-4">검색 결과가 없습니다.</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
response.data.forEach(item => {
|
||||
let categoryBadge = '';
|
||||
switch(item.category) {
|
||||
case '일반의약품':
|
||||
categoryBadge = '<span class="badge bg-success">일반</span>';
|
||||
break;
|
||||
case '전문의약품':
|
||||
categoryBadge = '<span class="badge bg-warning text-dark">전문</span>';
|
||||
break;
|
||||
case '한약재':
|
||||
categoryBadge = '<span class="badge bg-info">한약재</span>';
|
||||
break;
|
||||
case '원료의약품':
|
||||
categoryBadge = '<span class="badge bg-secondary">원료</span>';
|
||||
break;
|
||||
default:
|
||||
categoryBadge = `<span class="badge bg-light text-dark">${item.category || '-'}</span>`;
|
||||
}
|
||||
|
||||
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(`
|
||||
<tr>
|
||||
<td>
|
||||
<strong>${cleanName}</strong>
|
||||
${item.form_type ? `<br><small class="text-muted">${item.form_type}</small>` : ''}
|
||||
</td>
|
||||
<td><small>${item.company_name || '-'}</small></td>
|
||||
<td><small>${item.spec || '-'}</small></td>
|
||||
<td><small>${item.package_type || '-'}</small></td>
|
||||
<td>${categoryBadge}</td>
|
||||
<td><small class="text-monospace">${item.standard_code || '-'}</small></td>
|
||||
<td><small class="text-muted">${notes.length > 30 ? notes.substring(0, 30) + '...' : notes}</small></td>
|
||||
<td class="text-nowrap">
|
||||
<button class="btn btn-sm btn-outline-info med-detail-btn"
|
||||
data-item='${itemJson}' title="상세보기">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-success med-add-cart-btn"
|
||||
data-item='${itemJson}'
|
||||
data-code="${item.standard_code}"
|
||||
title="장바구니 담기"
|
||||
${inCart ? 'disabled' : ''}>
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
// 상세보기 이벤트
|
||||
$('.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(`<tr><td colspan="8" class="text-center text-danger py-4">검색 실패: ${xhr.responseJSON?.error || '서버 오류'}</td></tr>`);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── 상세보기 모달 ─────────────────────────────────────
|
||||
|
||||
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 = '<table class="table table-sm">';
|
||||
fields.forEach(f => {
|
||||
if (f.value) {
|
||||
html += `<tr><th width="140" class="text-muted">${f.label}</th><td>${f.value}</td></tr>`;
|
||||
}
|
||||
});
|
||||
html += '</table>';
|
||||
|
||||
$('#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 =>
|
||||
`<option value="${o}" ${item.origin_country === o ? 'selected' : ''}>${o}</option>`
|
||||
).join('');
|
||||
|
||||
const specDisplay = item.spec_grams
|
||||
? `${item.spec} <br><small class="text-success">${item.spec_grams.toLocaleString()}g</small>`
|
||||
: `${item.spec || '-'} <br><small class="text-danger">수동입력</small>`;
|
||||
|
||||
tbody.append(`
|
||||
<tr data-cart-idx="${idx}">
|
||||
<td><small><strong>${item.product_name}</strong></small></td>
|
||||
<td><small>${item.company_name || '-'}</small></td>
|
||||
<td>${specDisplay}</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm text-end cart-qty"
|
||||
data-idx="${idx}" value="${item.qty || 1}"
|
||||
min="1" step="1" placeholder="1">
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm text-end cart-unit-price"
|
||||
data-idx="${idx}" value="${item.unit_price || ''}"
|
||||
min="0" step="100" placeholder="35000">
|
||||
</td>
|
||||
<td class="text-end cart-amt" data-idx="${idx}">
|
||||
${amt ? amt.toLocaleString() : '-'}
|
||||
</td>
|
||||
<td class="text-end cart-ppg text-muted" data-idx="${idx}">
|
||||
<small>${pricePerG ? pricePerG.toFixed(1) : '-'}</small>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm cart-origin" data-idx="${idx}">
|
||||
${originOptions}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger cart-remove-btn" data-idx="${idx}" title="삭제">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
|
||||
$('#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(`<small>${pricePerG ? pricePerG.toFixed(1) : '-'}</small>`);
|
||||
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(`<option value="${s.supplier_id}">${s.name}</option>`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─── 초기화 ────────────────────────────────────────────
|
||||
|
||||
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();
|
||||
};
|
||||
})();
|
||||
163
templates/medicine_master.html
Normal file
163
templates/medicine_master.html
Normal file
@ -0,0 +1,163 @@
|
||||
<!-- 의약품 마스터 검색 페이지 -->
|
||||
<div id="medicine-master" class="main-content">
|
||||
<h3 class="mb-4"><i class="bi bi-search"></i> 의약품 마스터 검색</h3>
|
||||
|
||||
<!-- 검색 영역 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">검색어</label>
|
||||
<input type="text" class="form-control" id="medSearchInput"
|
||||
placeholder="상품명, 업체명, 표준코드 검색..." autofocus>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">분류</label>
|
||||
<select class="form-select" id="medCategoryFilter">
|
||||
<option value="">전체</option>
|
||||
<option value="일반의약품">일반의약품</option>
|
||||
<option value="전문의약품">전문의약품</option>
|
||||
<option value="한약재">한약재</option>
|
||||
<option value="원료의약품">원료의약품</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">포장형태</label>
|
||||
<select class="form-select" id="medPackageFilter">
|
||||
<option value="">전체</option>
|
||||
<option value="병">병</option>
|
||||
<option value="포">포</option>
|
||||
<option value="박스">박스</option>
|
||||
<option value="기타">기타</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button class="btn btn-primary w-100" id="medSearchBtn">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<span class="text-muted" id="medSearchCount"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 검색 결과 -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">검색 결과</h5>
|
||||
<div>
|
||||
<span class="badge bg-primary" id="medResultCount">0</span>건
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive" style="max-height: 600px; overflow-y: auto;">
|
||||
<table class="table table-hover table-sm mb-0">
|
||||
<thead class="table-light sticky-top">
|
||||
<tr>
|
||||
<th>상품명</th>
|
||||
<th>업체명</th>
|
||||
<th>규격</th>
|
||||
<th>포장</th>
|
||||
<th>분류</th>
|
||||
<th>표준코드</th>
|
||||
<th>비고</th>
|
||||
<th width="100">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="medSearchResults">
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted py-5">
|
||||
<i class="bi bi-search" style="font-size: 2rem;"></i>
|
||||
<p class="mt-2">검색어를 입력하세요 (2자 이상)</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 입고 장바구니 -->
|
||||
<div class="card mt-4" id="cartPanel" style="display:none;">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-cart3"></i> 입고 장바구니 (<span id="cartCount">0</span>건)</h5>
|
||||
<button class="btn btn-sm btn-outline-danger" id="cartClearBtn" title="비우기">
|
||||
<i class="bi bi-trash"></i> 비우기
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 입고 정보 -->
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">도매상 <span class="text-danger">*</span></label>
|
||||
<select class="form-select form-select-sm" id="cartSupplier">
|
||||
<option value="">-- 선택 --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">입고일 <span class="text-danger">*</span></label>
|
||||
<input type="date" class="form-control form-control-sm" id="cartReceiptDate">
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">비고</label>
|
||||
<input type="text" class="form-control form-control-sm" id="cartNotes" placeholder="비고 입력...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 장바구니 테이블 -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>상품명</th>
|
||||
<th>업체</th>
|
||||
<th>규격</th>
|
||||
<th width="70">수량</th>
|
||||
<th width="110">단가(원)</th>
|
||||
<th width="90">금액</th>
|
||||
<th width="80">g당단가</th>
|
||||
<th width="100">원산지</th>
|
||||
<th width="40"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cartBody"></tbody>
|
||||
<tfoot class="table-light">
|
||||
<tr class="fw-bold">
|
||||
<td colspan="3" class="text-end">합계</td>
|
||||
<td id="cartTotalQty" class="text-end">0</td>
|
||||
<td></td>
|
||||
<td id="cartTotalAmt" class="text-end">0</td>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 입고장 생성 버튼 -->
|
||||
<div class="text-end mt-3">
|
||||
<button class="btn btn-primary" id="createReceiptBtn">
|
||||
<i class="bi bi-clipboard-check"></i> 입고장 생성
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 제품 상세 모달 -->
|
||||
<div class="modal fade" id="medDetailModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="medDetailTitle">제품 상세</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="medDetailBody">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user