feat(purchase): 구매신청 검색/직접입력/사진첨부/HEIC 지원/마스터 자동등록
- 소모품 select → 검색형 드롭다운 (debounce + 키보드 탐색) - 미등록 품목 직접 입력 + 분류 선택 지원 - 사진 첨부 (base64 업로드, HEIC→JPEG 프론트 변환) - 구매 처리 시 미등록 품목 소모품 마스터 자동 등록 - item_id NULL 허용, LEFT JOIN, custom_item_name/custom_category/photo_path 컬럼 - DB 마이그레이션 필요: ALTER TABLE purchase_requests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,18 @@
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="/static/css/tkfb.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/heic2any@0.0.4/dist/heic2any.min.js"></script>
|
||||
<style>
|
||||
.item-dropdown { position: absolute; top: 100%; left: 0; right: 0; max-height: 280px; overflow-y: auto; background: white; border: 1px solid #e5e7eb; border-top: none; border-radius: 0 0 8px 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 20; display: none; }
|
||||
.item-dropdown.open { display: block; }
|
||||
.item-dropdown-item { padding: 8px 12px; cursor: pointer; font-size: 14px; display: flex; align-items: center; gap: 8px; }
|
||||
.item-dropdown-item:hover, .item-dropdown-item.active { background: #fff7ed; }
|
||||
.item-dropdown-item .cat-tag { font-size: 11px; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }
|
||||
.item-dropdown-custom { padding: 10px 12px; cursor: pointer; font-size: 14px; color: #ea580c; border-top: 1px solid #f3f4f6; display: flex; align-items: center; gap: 6px; }
|
||||
.item-dropdown-custom:hover { background: #fff7ed; }
|
||||
.photo-preview-container { position: relative; display: inline-block; }
|
||||
.photo-preview-container .remove-btn { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; background: #ef4444; color: white; border: none; cursor: pointer; font-size: 11px; display: flex; align-items: center; justify-content: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<header class="bg-orange-700 text-white sticky top-0 z-50">
|
||||
@@ -41,12 +53,13 @@
|
||||
<!-- 구매신청 폼 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 mb-6">
|
||||
<h2 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-plus-circle text-orange-500 mr-2"></i>신규 구매신청</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4 items-end">
|
||||
<div class="sm:col-span-2">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4 items-start">
|
||||
<div class="sm:col-span-2 relative">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">소모품 <span class="text-red-400">*</span></label>
|
||||
<select id="prItemSelect" class="w-full px-3 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-orange-300" onchange="onItemSelect()">
|
||||
<option value="">소모품 선택</option>
|
||||
</select>
|
||||
<input type="text" id="prItemSearch" class="w-full px-3 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-orange-300" placeholder="소모품 검색 또는 직접 입력" autocomplete="off">
|
||||
<input type="hidden" id="prItemId" value="">
|
||||
<input type="hidden" id="prCustomItemName" value="">
|
||||
<div id="prItemDropdown" class="item-dropdown"></div>
|
||||
<div id="prItemPreview" class="mt-2 hidden flex items-center gap-3 p-2 bg-gray-50 rounded-lg">
|
||||
<img id="prItemPhoto" class="w-12 h-12 rounded object-cover hidden">
|
||||
<div>
|
||||
@@ -54,6 +67,16 @@
|
||||
<div id="prItemPrice" class="text-xs text-gray-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 직접 입력 시 분류 선택 -->
|
||||
<div id="prCustomCategoryWrap" class="mt-2 hidden">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">분류 <span class="text-red-400">*</span></label>
|
||||
<select id="prCustomCategory" class="w-full px-3 py-2 border rounded-lg text-sm">
|
||||
<option value="consumable">소모품</option>
|
||||
<option value="safety">안전용품</option>
|
||||
<option value="repair">수선비</option>
|
||||
<option value="equipment">설비</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">수량 <span class="text-red-400">*</span></label>
|
||||
@@ -64,6 +87,21 @@
|
||||
<input type="text" id="prNotes" class="w-full px-3 py-2 border rounded-lg text-sm" placeholder="선택 사항">
|
||||
</div>
|
||||
</div>
|
||||
<!-- 사진 첨부 -->
|
||||
<div class="mt-3">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">사진 첨부 (선택)</label>
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="px-3 py-2 border rounded-lg text-sm text-gray-600 cursor-pointer hover:bg-gray-50 inline-flex items-center gap-1">
|
||||
<i class="fas fa-camera text-orange-400"></i> 사진 선택
|
||||
<input type="file" id="prPhotoInput" accept="image/*,.heic,.heif" class="hidden" onchange="onPhotoSelected(this)">
|
||||
</label>
|
||||
<div id="prPhotoPreview" class="hidden photo-preview-container">
|
||||
<img id="prPhotoPreviewImg" class="w-16 h-16 rounded object-cover">
|
||||
<button class="remove-btn" onclick="removePhoto()" title="삭제">×</button>
|
||||
</div>
|
||||
<span id="prPhotoStatus" class="text-xs text-gray-400"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button onclick="submitPurchaseRequest()" class="px-5 py-2 bg-orange-600 text-white rounded-lg text-sm hover:bg-orange-700">
|
||||
<i class="fas fa-paper-plane mr-1"></i>구매신청
|
||||
@@ -140,7 +178,15 @@
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">수량</label>
|
||||
<input type="number" id="pmQuantity" class="w-full px-3 py-2 border rounded-lg text-sm" min="1" value="1">
|
||||
</div>
|
||||
<div class="col-span-2" id="pmPriceDiffArea" class="hidden">
|
||||
<div class="col-span-2" id="pmPriceDiffArea">
|
||||
</div>
|
||||
<!-- 마스터 등록 체크박스 (미등록 품목일 때만 표시) -->
|
||||
<div class="col-span-2 hidden" id="pmMasterRegisterWrap">
|
||||
<label class="flex items-center gap-2 cursor-pointer p-2 bg-orange-50 rounded-lg">
|
||||
<input type="checkbox" id="pmRegisterToMaster" checked class="h-4 w-4 rounded text-orange-600">
|
||||
<span class="text-sm text-gray-700">소모품 마스터에 등록</span>
|
||||
<span class="text-xs text-gray-400">(구매 처리 시 자동 등록)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">메모</label>
|
||||
@@ -175,7 +221,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/tkfb-core.js?v=20260313"></script>
|
||||
<script src="/static/js/purchase-request.js?v=20260313"></script>
|
||||
<script src="/static/js/tkfb-core.js?v=20260313b"></script>
|
||||
<script src="/static/js/purchase-request.js?v=20260313b"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,6 +5,8 @@ const TKUSER_BASE_URL = location.hostname.includes('technicalkorea.net')
|
||||
|
||||
const CAT_LABELS = { consumable: '소모품', safety: '안전용품', repair: '수선비', equipment: '설비' };
|
||||
const CAT_COLORS = { consumable: 'badge-blue', safety: 'badge-green', repair: 'badge-amber', equipment: 'badge-purple' };
|
||||
const CAT_BG = { consumable: '#dbeafe', safety: '#dcfce7', repair: '#fef3c7', equipment: '#f3e8ff' };
|
||||
const CAT_FG = { consumable: '#1e40af', safety: '#166534', repair: '#92400e', equipment: '#7e22ce' };
|
||||
const STATUS_LABELS = { pending: '대기', purchased: '구매완료', hold: '보류' };
|
||||
const STATUS_COLORS = { pending: 'badge-amber', purchased: 'badge-green', hold: 'badge-gray' };
|
||||
|
||||
@@ -14,6 +16,9 @@ let requestsList = [];
|
||||
let currentRequestForPurchase = null;
|
||||
let currentRequestForHold = null;
|
||||
let isAdmin = false;
|
||||
let photoBase64 = null;
|
||||
let searchDebounceTimer = null;
|
||||
let dropdownActiveIndex = -1;
|
||||
|
||||
async function loadInitialData() {
|
||||
try {
|
||||
@@ -23,45 +28,141 @@ async function loadInitialData() {
|
||||
]);
|
||||
consumableItems = itemsRes.data || [];
|
||||
vendorsList = vendorsRes.data || [];
|
||||
populateItemSelect();
|
||||
populateVendorSelect();
|
||||
} catch (e) {
|
||||
console.error('초기 데이터 로드 실패:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function populateItemSelect() {
|
||||
const sel = document.getElementById('prItemSelect');
|
||||
const groups = {};
|
||||
consumableItems.forEach(item => {
|
||||
const cat = CAT_LABELS[item.category] || item.category;
|
||||
if (!groups[cat]) groups[cat] = [];
|
||||
groups[cat].push(item);
|
||||
});
|
||||
let html = '<option value="">소모품 선택</option>';
|
||||
for (const [cat, items] of Object.entries(groups)) {
|
||||
html += `<optgroup label="${cat}">`;
|
||||
items.forEach(item => {
|
||||
const maker = item.maker ? ` (${escapeHtml(item.maker)})` : '';
|
||||
html += `<option value="${item.item_id}">${escapeHtml(item.item_name)}${maker}</option>`;
|
||||
});
|
||||
html += '</optgroup>';
|
||||
}
|
||||
sel.innerHTML = html;
|
||||
}
|
||||
|
||||
function populateVendorSelect() {
|
||||
const sel = document.getElementById('pmVendor');
|
||||
sel.innerHTML = '<option value="">업체 선택 (선택사항)</option>' +
|
||||
vendorsList.map(v => `<option value="${v.vendor_id}">${escapeHtml(v.vendor_name)}</option>`).join('');
|
||||
}
|
||||
|
||||
function onItemSelect() {
|
||||
const itemId = parseInt(document.getElementById('prItemSelect').value);
|
||||
const preview = document.getElementById('prItemPreview');
|
||||
const item = consumableItems.find(i => i.item_id === itemId);
|
||||
if (!item) { preview.classList.add('hidden'); return; }
|
||||
/* ===== 검색형 품목 선택 ===== */
|
||||
function initItemSearch() {
|
||||
const input = document.getElementById('prItemSearch');
|
||||
const dropdown = document.getElementById('prItemDropdown');
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(searchDebounceTimer);
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
const query = input.value.trim();
|
||||
if (query.length === 0) {
|
||||
// 빈 입력: 전체 목록 보여주기
|
||||
showDropdown(consumableItems.slice(0, 30), '');
|
||||
} else {
|
||||
const lower = query.toLowerCase();
|
||||
const filtered = consumableItems.filter(item =>
|
||||
item.item_name.toLowerCase().includes(lower) ||
|
||||
(item.maker && item.maker.toLowerCase().includes(lower))
|
||||
);
|
||||
showDropdown(filtered, query);
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
input.addEventListener('focus', () => {
|
||||
const query = input.value.trim();
|
||||
if (query.length === 0) {
|
||||
showDropdown(consumableItems.slice(0, 30), '');
|
||||
} else {
|
||||
const lower = query.toLowerCase();
|
||||
const filtered = consumableItems.filter(item =>
|
||||
item.item_name.toLowerCase().includes(lower) ||
|
||||
(item.maker && item.maker.toLowerCase().includes(lower))
|
||||
);
|
||||
showDropdown(filtered, query);
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
const items = dropdown.querySelectorAll('.item-dropdown-item, .item-dropdown-custom');
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
dropdownActiveIndex = Math.min(dropdownActiveIndex + 1, items.length - 1);
|
||||
updateDropdownActive(items);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
dropdownActiveIndex = Math.max(dropdownActiveIndex - 1, 0);
|
||||
updateDropdownActive(items);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (dropdownActiveIndex >= 0 && dropdownActiveIndex < items.length) {
|
||||
items[dropdownActiveIndex].click();
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// 외부 클릭 시 드롭다운 닫기
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('#prItemSearch') && !e.target.closest('#prItemDropdown')) {
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showDropdown(items, query) {
|
||||
const dropdown = document.getElementById('prItemDropdown');
|
||||
dropdownActiveIndex = -1;
|
||||
|
||||
let html = '';
|
||||
if (items.length > 0) {
|
||||
items.forEach((item, idx) => {
|
||||
const catLabel = CAT_LABELS[item.category] || item.category;
|
||||
const bg = CAT_BG[item.category] || '#f3f4f6';
|
||||
const fg = CAT_FG[item.category] || '#374151';
|
||||
const maker = item.maker ? ` (${escapeHtml(item.maker)})` : '';
|
||||
html += `<div class="item-dropdown-item" data-item-id="${item.item_id}" onclick="selectItem(${item.item_id})">
|
||||
<span class="cat-tag" style="background:${bg};color:${fg}">${catLabel}</span>
|
||||
<span>${escapeHtml(item.item_name)}${maker}</span>
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// 직접 입력 옵션 (검색어가 있을 때만)
|
||||
if (query.length > 0) {
|
||||
html += `<div class="item-dropdown-custom" onclick="selectCustomItem()">
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
<span>"${escapeHtml(query)}"(으)로 직접 신청</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (html) {
|
||||
dropdown.innerHTML = html;
|
||||
dropdown.classList.add('open');
|
||||
} else {
|
||||
closeDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDropdownActive(items) {
|
||||
items.forEach((el, idx) => {
|
||||
el.classList.toggle('active', idx === dropdownActiveIndex);
|
||||
if (idx === dropdownActiveIndex) el.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
document.getElementById('prItemDropdown').classList.remove('open');
|
||||
dropdownActiveIndex = -1;
|
||||
}
|
||||
|
||||
function selectItem(itemId) {
|
||||
const item = consumableItems.find(i => i.item_id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
const input = document.getElementById('prItemSearch');
|
||||
input.value = item.item_name + (item.maker ? ' (' + item.maker + ')' : '');
|
||||
document.getElementById('prItemId').value = item.item_id;
|
||||
document.getElementById('prCustomItemName').value = '';
|
||||
closeDropdown();
|
||||
|
||||
// 미리보기
|
||||
const preview = document.getElementById('prItemPreview');
|
||||
preview.classList.remove('hidden');
|
||||
const photoEl = document.getElementById('prItemPhoto');
|
||||
if (item.photo_path) {
|
||||
@@ -74,27 +175,114 @@ function onItemSelect() {
|
||||
document.getElementById('prItemInfo').textContent = `${item.item_name} ${item.maker ? '(' + item.maker + ')' : ''}`;
|
||||
const price = item.base_price ? Number(item.base_price).toLocaleString() + '원/' + (item.unit || 'EA') : '기준가 미설정';
|
||||
document.getElementById('prItemPrice').textContent = price;
|
||||
|
||||
// 분류 선택 숨김
|
||||
document.getElementById('prCustomCategoryWrap').classList.add('hidden');
|
||||
}
|
||||
|
||||
function selectCustomItem() {
|
||||
const input = document.getElementById('prItemSearch');
|
||||
const customName = input.value.trim();
|
||||
if (!customName) return;
|
||||
|
||||
document.getElementById('prItemId').value = '';
|
||||
document.getElementById('prCustomItemName').value = customName;
|
||||
closeDropdown();
|
||||
|
||||
// 미리보기 숨기고 분류 선택 표시
|
||||
document.getElementById('prItemPreview').classList.add('hidden');
|
||||
document.getElementById('prCustomCategoryWrap').classList.remove('hidden');
|
||||
}
|
||||
|
||||
/* ===== 사진 첨부 ===== */
|
||||
async function onPhotoSelected(inputEl) {
|
||||
const file = inputEl.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const statusEl = document.getElementById('prPhotoStatus');
|
||||
let processFile = file;
|
||||
|
||||
// HEIC/HEIF 변환
|
||||
const isHeic = file.type === 'image/heic' || file.type === 'image/heif' ||
|
||||
file.name.toLowerCase().endsWith('.heic') || file.name.toLowerCase().endsWith('.heif');
|
||||
if (isHeic) {
|
||||
if (typeof heic2any === 'undefined') {
|
||||
showToast('HEIC 변환 라이브러리를 불러오지 못했습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = 'HEIC 변환 중...';
|
||||
try {
|
||||
const blob = await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.85 });
|
||||
processFile = new File([blob], file.name.replace(/\.heic$/i, '.jpg').replace(/\.heif$/i, '.jpg'), { type: 'image/jpeg' });
|
||||
} catch (e) {
|
||||
console.error('HEIC 변환 실패:', e);
|
||||
showToast('HEIC 이미지 변환에 실패했습니다.', 'error');
|
||||
statusEl.textContent = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 파일 크기 확인 (10MB)
|
||||
if (processFile.size > 10 * 1024 * 1024) {
|
||||
showToast('파일 크기는 10MB 이하만 가능합니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.textContent = '처리 중...';
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
photoBase64 = e.target.result;
|
||||
document.getElementById('prPhotoPreviewImg').src = photoBase64;
|
||||
document.getElementById('prPhotoPreview').classList.remove('hidden');
|
||||
statusEl.textContent = '';
|
||||
};
|
||||
reader.readAsDataURL(processFile);
|
||||
}
|
||||
|
||||
function removePhoto() {
|
||||
photoBase64 = null;
|
||||
document.getElementById('prPhotoPreview').classList.add('hidden');
|
||||
document.getElementById('prPhotoInput').value = '';
|
||||
document.getElementById('prPhotoStatus').textContent = '';
|
||||
}
|
||||
|
||||
/* ===== 구매신청 제출 ===== */
|
||||
async function submitPurchaseRequest() {
|
||||
const item_id = document.getElementById('prItemSelect').value;
|
||||
const itemId = document.getElementById('prItemId').value;
|
||||
const customItemName = document.getElementById('prCustomItemName').value;
|
||||
const quantity = parseInt(document.getElementById('prQuantity').value) || 0;
|
||||
const notes = document.getElementById('prNotes').value.trim();
|
||||
|
||||
if (!item_id) { showToast('소모품을 선택해주세요.', 'error'); return; }
|
||||
if (!itemId && !customItemName) { showToast('소모품을 선택하거나 품목명을 입력해주세요.', 'error'); return; }
|
||||
if (quantity < 1) { showToast('수량은 1 이상이어야 합니다.', 'error'); return; }
|
||||
|
||||
const body = { quantity, notes };
|
||||
if (itemId) {
|
||||
body.item_id = parseInt(itemId);
|
||||
} else {
|
||||
body.custom_item_name = customItemName;
|
||||
body.custom_category = document.getElementById('prCustomCategory').value;
|
||||
}
|
||||
if (photoBase64) {
|
||||
body.photo = photoBase64;
|
||||
}
|
||||
|
||||
try {
|
||||
await api('/purchase-requests', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ item_id: parseInt(item_id), quantity, notes })
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
showToast('구매신청이 등록되었습니다.');
|
||||
document.getElementById('prItemSelect').value = '';
|
||||
// 폼 초기화
|
||||
document.getElementById('prItemSearch').value = '';
|
||||
document.getElementById('prItemId').value = '';
|
||||
document.getElementById('prCustomItemName').value = '';
|
||||
document.getElementById('prQuantity').value = '1';
|
||||
document.getElementById('prNotes').value = '';
|
||||
document.getElementById('prItemPreview').classList.add('hidden');
|
||||
document.getElementById('prCustomCategoryWrap').classList.add('hidden');
|
||||
removePhoto();
|
||||
await loadRequests();
|
||||
} catch (e) { showToast(e.message, 'error'); }
|
||||
}
|
||||
@@ -122,11 +310,22 @@ function renderRequests() {
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = requestsList.map(r => {
|
||||
const catLabel = CAT_LABELS[r.category] || r.category;
|
||||
const catColor = CAT_COLORS[r.category] || 'badge-gray';
|
||||
// 등록 품목이면 ci 데이터, 미등록이면 custom 데이터 사용
|
||||
const itemName = r.item_name || r.custom_item_name || '-';
|
||||
const category = r.category || r.custom_category;
|
||||
const catLabel = CAT_LABELS[category] || category || '-';
|
||||
const catColor = CAT_COLORS[category] || 'badge-gray';
|
||||
const statusLabel = STATUS_LABELS[r.status] || r.status;
|
||||
const statusColor = STATUS_COLORS[r.status] || 'badge-gray';
|
||||
const photoSrc = r.photo_path ? TKUSER_BASE_URL + r.photo_path : '';
|
||||
const isCustom = !r.item_id && r.custom_item_name;
|
||||
|
||||
// 사진: 구매신청 첨부 사진 우선, 없으면 소모품 마스터 사진
|
||||
let photoSrc = '';
|
||||
if (r.pr_photo_path) {
|
||||
photoSrc = r.pr_photo_path;
|
||||
} else if (r.ci_photo_path) {
|
||||
photoSrc = TKUSER_BASE_URL + r.ci_photo_path;
|
||||
}
|
||||
|
||||
let actions = '';
|
||||
if (isAdmin && r.status === 'pending') {
|
||||
@@ -144,7 +343,7 @@ function renderRequests() {
|
||||
<div class="flex items-center gap-2">
|
||||
${photoSrc ? `<img src="${photoSrc}" class="w-8 h-8 rounded object-cover" onerror="this.style.display='none'">` : ''}
|
||||
<div>
|
||||
<div class="font-medium text-gray-800">${escapeHtml(r.item_name)}</div>
|
||||
<div class="font-medium text-gray-800">${escapeHtml(itemName)}${isCustom ? ' <span class="text-xs text-orange-500">(직접입력)</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500">${escapeHtml(r.maker || '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,16 +367,31 @@ function openPurchaseModal(requestId) {
|
||||
if (!r) return;
|
||||
currentRequestForPurchase = r;
|
||||
|
||||
const itemName = r.item_name || r.custom_item_name || '-';
|
||||
const category = r.category || r.custom_category;
|
||||
const isCustom = !r.item_id && r.custom_item_name;
|
||||
const basePrice = r.base_price ? Number(r.base_price).toLocaleString() + '원' : '-';
|
||||
|
||||
document.getElementById('purchaseModalInfo').innerHTML = `
|
||||
<div class="font-medium">${escapeHtml(r.item_name)} ${r.maker ? '(' + escapeHtml(r.maker) + ')' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">분류: ${CAT_LABELS[r.category] || r.category} | 기준가: ${basePrice} | 신청수량: ${r.quantity}</div>
|
||||
<div class="font-medium">${escapeHtml(itemName)} ${r.maker ? '(' + escapeHtml(r.maker) + ')' : ''}${isCustom ? ' <span class="text-orange-500 text-xs">(직접입력)</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">분류: ${CAT_LABELS[category] || category || '-'} | 기준가: ${basePrice} | 신청수량: ${r.quantity}</div>
|
||||
${r.pr_photo_path ? `<img src="${r.pr_photo_path}" class="mt-2 w-20 h-20 rounded object-cover" onerror="this.style.display='none'">` : ''}
|
||||
`;
|
||||
document.getElementById('pmUnitPrice').value = r.base_price || '';
|
||||
document.getElementById('pmQuantity').value = r.quantity;
|
||||
document.getElementById('pmDate').value = new Date().toISOString().substring(0, 10);
|
||||
document.getElementById('pmNotes').value = '';
|
||||
document.getElementById('pmPriceDiffArea').innerHTML = '';
|
||||
|
||||
// 마스터 등록 체크박스: 미등록 품목일 때만 표시
|
||||
const masterWrap = document.getElementById('pmMasterRegisterWrap');
|
||||
if (isCustom) {
|
||||
masterWrap.classList.remove('hidden');
|
||||
document.getElementById('pmRegisterToMaster').checked = true;
|
||||
} else {
|
||||
masterWrap.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('purchaseModal').classList.remove('hidden');
|
||||
showPriceDiff();
|
||||
}
|
||||
@@ -218,14 +432,18 @@ async function submitPurchase() {
|
||||
if (!purchase_date) { showToast('구매일을 입력해주세요.', 'error'); return; }
|
||||
|
||||
const updateCheckbox = document.getElementById('pmUpdateBasePrice');
|
||||
const registerCheckbox = document.getElementById('pmRegisterToMaster');
|
||||
const isCustom = !currentRequestForPurchase.item_id && currentRequestForPurchase.custom_item_name;
|
||||
|
||||
const body = {
|
||||
request_id: currentRequestForPurchase.request_id,
|
||||
item_id: currentRequestForPurchase.item_id,
|
||||
item_id: currentRequestForPurchase.item_id || null,
|
||||
vendor_id: parseInt(document.getElementById('pmVendor').value) || null,
|
||||
quantity: parseInt(document.getElementById('pmQuantity').value) || currentRequestForPurchase.quantity,
|
||||
unit_price,
|
||||
purchase_date,
|
||||
update_base_price: updateCheckbox ? updateCheckbox.checked : false,
|
||||
register_to_master: isCustom ? (registerCheckbox ? registerCheckbox.checked : true) : undefined,
|
||||
notes: document.getElementById('pmNotes').value.trim()
|
||||
};
|
||||
|
||||
@@ -289,6 +507,7 @@ async function deleteRequest(requestId) {
|
||||
(async function() {
|
||||
if (!await initAuth()) return;
|
||||
isAdmin = currentUser && ['admin', 'system', 'system admin'].includes(currentUser.role);
|
||||
initItemSearch();
|
||||
await loadInitialData();
|
||||
await loadRequests();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user