feat(consumable): 소모품 마스터에 "규격(spec)" 필드 추가

품목의 규격 정보(예: 4" 용접, M16)를 분리 저장할 수 있도록 spec 컬럼 추가.
DB ALTER 필요: ALTER TABLE consumable_items ADD COLUMN spec VARCHAR(200) DEFAULT NULL AFTER item_name;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-16 13:34:43 +09:00
parent cc47d25851
commit 0a05bd8d76
10 changed files with 40 additions and 23 deletions

View File

@@ -108,6 +108,6 @@
</div>
</div>
<script src="/static/js/tkfb-core.js?v=2026031601"></script>
<script src="/static/js/purchase-analysis.js?v=2026031601"></script>
<script src="/static/js/purchase-analysis.js?v=2026031602"></script>
</body>
</html>

View File

@@ -222,6 +222,6 @@
</div>
</div>
<script src="/static/js/tkfb-core.js?v=2026031601"></script>
<script src="/static/js/purchase-request.js?v=2026031601"></script>
<script src="/static/js/purchase-request.js?v=2026031602"></script>
</body>
</html>

View File

@@ -102,7 +102,7 @@ function renderPurchaseList(data) {
return `<tr class="hover:bg-gray-50 ${hasPriceDiff ? 'bg-yellow-50' : ''}">
<td class="px-4 py-3">
<div class="font-medium text-gray-800">${escapeHtml(p.item_name)}</div>
<div class="font-medium text-gray-800">${escapeHtml(p.item_name)}${p.spec ? ' <span class="text-gray-400">[' + escapeHtml(p.spec) + ']</span>' : ''}</div>
<div class="text-xs text-gray-500">${escapeHtml(p.maker || '')}</div>
</td>
<td class="px-4 py-3"><span class="px-1.5 py-0.5 rounded text-xs ${catColor}">${catLabel}</span></td>
@@ -138,7 +138,7 @@ function renderPriceChanges(data) {
const arrow = diff > 0 ? '&#9650;' : '&#9660;';
const color = diff > 0 ? 'text-red-600' : 'text-blue-600';
return `<tr class="hover:bg-gray-50">
<td class="px-4 py-3">${escapeHtml(p.item_name)} ${p.maker ? '(' + escapeHtml(p.maker) + ')' : ''}</td>
<td class="px-4 py-3">${escapeHtml(p.item_name)}${p.spec ? ' [' + escapeHtml(p.spec) + ']' : ''} ${p.maker ? '(' + escapeHtml(p.maker) + ')' : ''}</td>
<td class="px-4 py-3 text-right">${Number(p.base_price).toLocaleString()}원</td>
<td class="px-4 py-3 text-right font-medium ${color}">${Number(p.unit_price).toLocaleString()}원</td>
<td class="px-4 py-3 text-right ${color}">${arrow} ${Math.abs(diff).toLocaleString()}원</td>

View File

@@ -10,6 +10,8 @@ const CAT_FG = { consumable: '#1e40af', safety: '#166534', repair: '#92400e', eq
const STATUS_LABELS = { pending: '대기', purchased: '구매완료', hold: '보류' };
const STATUS_COLORS = { pending: 'badge-amber', purchased: 'badge-green', hold: 'badge-gray' };
function _fmtSpec(spec) { return spec ? ' [' + spec + ']' : ''; }
let consumableItems = [];
let vendorsList = [];
let requestsList = [];
@@ -56,7 +58,8 @@ function initItemSearch() {
const lower = query.toLowerCase();
const filtered = consumableItems.filter(item =>
item.item_name.toLowerCase().includes(lower) ||
(item.maker && item.maker.toLowerCase().includes(lower))
(item.maker && item.maker.toLowerCase().includes(lower)) ||
(item.spec && item.spec.toLowerCase().includes(lower))
);
showDropdown(filtered, query);
}
@@ -71,7 +74,8 @@ function initItemSearch() {
const lower = query.toLowerCase();
const filtered = consumableItems.filter(item =>
item.item_name.toLowerCase().includes(lower) ||
(item.maker && item.maker.toLowerCase().includes(lower))
(item.maker && item.maker.toLowerCase().includes(lower)) ||
(item.spec && item.spec.toLowerCase().includes(lower))
);
showDropdown(filtered, query);
}
@@ -115,10 +119,11 @@ function showDropdown(items, query) {
const catLabel = CAT_LABELS[item.category] || item.category;
const bg = CAT_BG[item.category] || '#f3f4f6';
const fg = CAT_FG[item.category] || '#374151';
const spec = _fmtSpec(item.spec ? escapeHtml(item.spec) : '');
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>
<span>${escapeHtml(item.item_name)}${spec}${maker}</span>
</div>`;
});
}
@@ -156,7 +161,7 @@ function selectItem(itemId) {
if (!item) return;
const input = document.getElementById('prItemSearch');
input.value = item.item_name + (item.maker ? ' (' + item.maker + ')' : '');
input.value = item.item_name + _fmtSpec(item.spec) + (item.maker ? ' (' + item.maker + ')' : '');
document.getElementById('prItemId').value = item.item_id;
document.getElementById('prCustomItemName').value = '';
closeDropdown();
@@ -172,7 +177,7 @@ function selectItem(itemId) {
} else {
photoEl.classList.add('hidden');
}
document.getElementById('prItemInfo').textContent = `${item.item_name} ${item.maker ? '(' + item.maker + ')' : ''}`;
document.getElementById('prItemInfo').textContent = `${item.item_name}${_fmtSpec(item.spec)} ${item.maker ? '(' + item.maker + ')' : ''}`;
const price = item.base_price ? Number(item.base_price).toLocaleString() + '원/' + (item.unit || 'EA') : '기준가 미설정';
document.getElementById('prItemPrice').textContent = price;
@@ -343,7 +348,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(itemName)}${isCustom ? ' <span class="text-xs text-orange-500">(직접입력)</span>' : ''}</div>
<div class="font-medium text-gray-800">${escapeHtml(itemName)}${r.spec ? ' <span class="text-gray-400">[' + escapeHtml(r.spec) + ']</span>' : ''}${isCustom ? ' <span class="text-xs text-orange-500">(직접입력)</span>' : ''}</div>
<div class="text-xs text-gray-500">${escapeHtml(r.maker || '')}</div>
</div>
</div>
@@ -373,7 +378,7 @@ function openPurchaseModal(requestId) {
const basePrice = r.base_price ? Number(r.base_price).toLocaleString() + '원' : '-';
document.getElementById('purchaseModalInfo').innerHTML = `
<div class="font-medium">${escapeHtml(itemName)} ${r.maker ? '(' + escapeHtml(r.maker) + ')' : ''}${isCustom ? ' <span class="text-orange-500 text-xs">(직접입력)</span>' : ''}</div>
<div class="font-medium">${escapeHtml(itemName)}${_fmtSpec(r.spec ? escapeHtml(r.spec) : '')} ${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'">` : ''}
`;