feat: Add bulk asset code generation in upload review modal

This commit is contained in:
2026-04-23 20:25:58 +09:00
parent 4b5e25fd3f
commit 1fbd297988

View File

@@ -1,6 +1,7 @@
import { openModal, closeModals } from './BaseModal'; import { openModal, closeModals } from './BaseModal';
import { createIcons, X, Check, Database, Save, FileSpreadsheet, Layers } from 'lucide'; import { createIcons, X, Check, Database, Save, FileSpreadsheet, Layers, RefreshCcw } from 'lucide';
import { state, loadMasterDataFromDB } from '../../core/state'; import { state, loadMasterDataFromDB } from '../../core/state';
import { TYPE_PREFIX_MAP } from './SharedData';
let parsedData: any = null; let parsedData: any = null;
let currentTab: string = ''; let currentTab: string = '';
@@ -37,6 +38,9 @@ const UPLOAD_PREVIEW_MODAL_HTML = `
<div style="display:flex; align-items:center; gap:0.5rem;"> <div style="display:flex; align-items:center; gap:0.5rem;">
<span id="current-tab-name" style="font-weight:700; font-size:16px;">선택된 탭 없음</span> <span id="current-tab-name" style="font-weight:700; font-size:16px;">선택된 탭 없음</span>
<span id="current-tab-count" class="badge badge-primary">0건</span> <span id="current-tab-count" class="badge badge-primary">0건</span>
<button id="btn-bulk-generate-codes" class="btn btn-outline btn-sm hidden" style="margin-left:1rem; height:28px; font-size:12px; padding:0 0.75rem;">
<i data-lucide="refresh-ccw" style="width:14px; height:14px; margin-right:4px;"></i> 자산코드 일괄 생성
</button>
</div> </div>
<div style="font-size:12px; color:var(--text-muted);"> <div style="font-size:12px; color:var(--text-muted);">
* 아래 데이터가 신규로 추가되거나 기존 데이터가 갱신됩니다. * 아래 데이터가 신규로 추가되거나 기존 데이터가 갱신됩니다.
@@ -72,6 +76,9 @@ export function initUploadPreviewModal(onSuccess?: () => void) {
document.getElementById('btn-confirm-upload')?.addEventListener('click', () => { document.getElementById('btn-confirm-upload')?.addEventListener('click', () => {
confirmUpload(); confirmUpload();
}); });
document.getElementById('btn-bulk-generate-codes')?.addEventListener('click', () => {
generateBulkCodes();
});
} }
export function openUploadPreview(data: any) { export function openUploadPreview(data: any) {
@@ -87,7 +94,7 @@ export function openUploadPreview(data: any) {
renderCurrentTable(); renderCurrentTable();
openModal('upload-preview-modal'); openModal('upload-preview-modal');
createIcons({ icons: { X, Check, Database, Save, FileSpreadsheet, Layers } }); createIcons({ icons: { X, Check, Database, Save, FileSpreadsheet, Layers, RefreshCcw } });
} }
function renderTabs() { function renderTabs() {
@@ -138,6 +145,13 @@ function renderCurrentTable() {
tabNameEl.textContent = currentTab; tabNameEl.textContent = currentTab;
tabCountEl.textContent = `${data.length}`; tabCountEl.textContent = `${data.length}`;
const generateBtn = document.getElementById('btn-bulk-generate-codes');
const isHwTab = ['개인PC', '서버', '스토리지', '전산비품', '모바일기기'].includes(currentTab);
if (generateBtn) {
if (isHwTab) generateBtn.classList.remove('hidden');
else generateBtn.classList.add('hidden');
}
if (!data || data.length === 0) { if (!data || data.length === 0) {
tableWrapper.innerHTML = '<div style="padding:4rem; text-align:center; color:var(--text-muted);">표시할 데이터가 없습니다.</div>'; tableWrapper.innerHTML = '<div style="padding:4rem; text-align:center; color:var(--text-muted);">표시할 데이터가 없습니다.</div>';
return; return;
@@ -222,3 +236,63 @@ async function confirmUpload() {
} }
} }
} }
async function generateBulkCodes() {
const data = parsedData[currentTab];
if (!data) return;
const generateBtn = document.getElementById('btn-bulk-generate-codes') as HTMLButtonElement;
if (generateBtn) {
generateBtn.disabled = true;
generateBtn.innerHTML = '<i data-lucide="refresh-ccw" class="animate-spin"></i> 생성 중...';
createIcons({ icons: { RefreshCcw } });
}
try {
// Group rows by prefix (type + purchase_ym)
const rowsToProcess = data.filter((r: any) => !r.);
if (rowsToProcess.length === 0) {
alert('이미 모든 항목에 자산코드가 부여되어 있습니다.');
return;
}
const groups: Record<string, any[]> = {};
rowsToProcess.forEach((r: any) => {
const type = r. || r. || r.type || 'ETC';
const typeCode = TYPE_PREFIX_MAP[type] || 'ETC';
const purchaseYM = String(r. || '').replace(/[^0-9]/g, '');
if (purchaseYM.length < 6) {
// Fallback or skip
return;
}
const prefix = `${typeCode}-${purchaseYM.substring(0, 6)}-`;
if (!groups[prefix]) groups[prefix] = [];
groups[prefix].push(r);
});
for (const prefix in groups) {
const rows = groups[prefix];
// Fetch current next code for this prefix
const res = await fetch(`http://172.16.40.100:3000/api/generate-asset-code?prefix=${prefix}`);
const result = await res.json();
if (result.nextCode) {
let baseNum = parseInt(result.nextCode.replace(prefix, ''));
rows.forEach((r, idx) => {
r. = `${prefix}${(baseNum + idx).toString().padStart(4, '0')}`;
});
}
}
renderCurrentTable();
alert(`${rowsToProcess.length}건의 자산코드가 생성되었습니다.`);
} catch (err) {
console.error(err);
alert('자산코드 생성 중 오류가 발생했습니다.');
} finally {
if (generateBtn) {
generateBtn.disabled = false;
generateBtn.innerHTML = '<i data-lucide="refresh-ccw"></i> 자산코드 일괄 생성';
createIcons({ icons: { RefreshCcw } });
}
}
}