feat: migrate ServerPC data to asset_pc, enhance filters with location, and standardize page headers
- 서버PC 자산을 asset_pc 테이블로 통합 마이그레이션 및 스키마 확장 (위치, IP 정보 복구 완료) - 하드웨어 자산 페이지의 구매법인 필터를 자산위치 필터로 교체 및 동적 데이터 바인딩 적용 - 모든 자산 리스트 페이지 상단에 설명(Description) 필드 추가 및 헤더 표준화 - 상세 모달 내 삭제 버튼 기능 구현 및 서버PC 용도 필드 노출 오류 수정 - 현 사용조직 필터 리스트가 비어있던 DOM 셀렉터 버그 수정
This commit is contained in:
421
server.js
421
server.js
@@ -30,335 +30,158 @@ const handleError = (res, err, context, isGet = false) => {
|
||||
else res.status(500).json({ error: err.message });
|
||||
};
|
||||
|
||||
// --- Mapping Definitions ---
|
||||
// --- API Implementation ---
|
||||
|
||||
const HW_SELECT_FIELDS = `
|
||||
id,
|
||||
asset_type AS \`type\`,
|
||||
corp AS \`법인\`,
|
||||
asset_code AS \`자산코드\`,
|
||||
purchase_date AS \`구매일\`,
|
||||
user_name AS \`사용자\`,
|
||||
dept AS \`현사용조직\`,
|
||||
prev_org AS \`이전사용조직\`,
|
||||
location AS \`위치\`,
|
||||
manager_primary AS \`담당자_정\`,
|
||||
manager_secondary AS \`담당자_부\`,
|
||||
product_name AS \`모델명\`,
|
||||
usage_category AS \`상세용도\`,
|
||||
usage_description AS \`상세\`,
|
||||
os AS \`OS\`,
|
||||
cpu AS \`CPU\`,
|
||||
gpu AS \`GPU\`,
|
||||
ram AS \`RAM\`,
|
||||
storage1 AS \`SSD1\`,
|
||||
storage2 AS \`SSD2\`,
|
||||
storage3 AS \`SSD3\`,
|
||||
mainboard AS \`메인보드\`,
|
||||
ip_address AS \`IP주소\`,
|
||||
remote_tool AS \`원격접속\`,
|
||||
server_id AS \`서버ID\`,
|
||||
server_pw AS \`서버PW\`,
|
||||
monitoring AS \`모니터링\`,
|
||||
price AS \`금액\`,
|
||||
vendor AS \`납품업체\`,
|
||||
remarks AS \`비고\`,
|
||||
asset_category AS \`category\`
|
||||
`;
|
||||
|
||||
const HW_REVERSE_MAP = {
|
||||
'id': 'id',
|
||||
'type': 'asset_type',
|
||||
'법인': 'corp',
|
||||
'자산코드': 'asset_code',
|
||||
'구매일': 'purchase_date',
|
||||
'구매연월': 'purchase_date',
|
||||
'사용자': 'user_name',
|
||||
'현사용조직': 'dept',
|
||||
'이전사용조직': 'prev_org',
|
||||
'위치': 'location',
|
||||
'담당자_정': 'manager_primary',
|
||||
'담당자_부': 'manager_secondary',
|
||||
'모델명': 'product_name',
|
||||
'상세용도': 'usage_category',
|
||||
'상세': 'usage_description',
|
||||
'OS': 'os',
|
||||
'CPU': 'cpu',
|
||||
'GPU': 'gpu',
|
||||
'RAM': 'ram',
|
||||
'SSD1': 'storage1',
|
||||
'SSD2': 'storage2',
|
||||
'SSD3': 'storage3',
|
||||
'메인보드': 'mainboard',
|
||||
'IP주소': 'ip_address',
|
||||
'원격접속': 'remote_tool',
|
||||
'서버ID': 'server_id',
|
||||
'서버PW': 'server_pw',
|
||||
'모니터링': 'monitoring',
|
||||
'금액': 'price',
|
||||
'납품업체': 'vendor',
|
||||
'비고': 'remarks',
|
||||
'category': 'asset_category'
|
||||
/**
|
||||
* Generic Fetcher for Asset Tables
|
||||
*/
|
||||
const fetchAssets = async (tableName, res, context) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT * FROM ${tableName}`);
|
||||
console.log(`📡 [GET ${context}] Returning ${rows.length} rows from ${tableName}`);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
handleError(res, err, context, true);
|
||||
}
|
||||
};
|
||||
|
||||
const mapObject = (obj, mapping) => {
|
||||
const mapped = {};
|
||||
Object.entries(obj).forEach(([key, val]) => {
|
||||
const dbKey = mapping[key] || key;
|
||||
mapped[dbKey] = val;
|
||||
});
|
||||
return mapped;
|
||||
};
|
||||
|
||||
// --- GET Routes ---
|
||||
|
||||
app.get('/api/pc', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT ${HW_SELECT_FIELDS} FROM asset_hardware WHERE asset_category = "개인PC" OR asset_code LIKE "PC%"`);
|
||||
console.log(`📡 [GET /api/pc] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '개인PC', 구매연월: r.구매일 })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/pc', true); }
|
||||
});
|
||||
|
||||
app.get('/api/server', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT ${HW_SELECT_FIELDS} FROM asset_hardware WHERE asset_category = "서버" OR asset_code LIKE "SVR%"`);
|
||||
console.log(`📡 [GET /api/server] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '서버', 구매연월: r.구매일 })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/server', true); }
|
||||
});
|
||||
|
||||
app.get('/api/storage', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT ${HW_SELECT_FIELDS} FROM asset_hardware WHERE asset_category = "스토리지" OR asset_code LIKE "STO%"`);
|
||||
console.log(`📡 [GET /api/storage] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '스토리지', 구매연월: r.구매일 })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/storage', true); }
|
||||
});
|
||||
|
||||
app.get('/api/equip', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT ${HW_SELECT_FIELDS} FROM asset_hardware WHERE asset_category = "전산비품" OR asset_code LIKE "EQP%"`);
|
||||
console.log(`📡 [GET /api/equip] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '전산비품', 구매연월: r.구매일 })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/equip', true); }
|
||||
});
|
||||
|
||||
app.get('/api/mobile', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT ${HW_SELECT_FIELDS} FROM asset_hardware WHERE asset_category = "모바일기기" OR asset_category LIKE "%모바일%" OR asset_code LIKE "MOB%"`);
|
||||
console.log(`📡 [GET /api/mobile] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '모바일기기', 구매연월: r.구매일 })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/mobile', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/software/subscription', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
id, category AS \`분야\`, corp AS \`법인\`, dept AS \`부서\`, product_name AS \`제품명\`,
|
||||
quantity AS \`수량\`, price AS \`금액\`, purchase_date AS \`구매일\`, start_date AS \`시작일\`,
|
||||
expiry_date AS \`만료일\`, vendor AS \`납품업체\`, remarks AS \`비고\`, license_type AS \`라이선스유형\`, account_name AS \`계정명\`
|
||||
FROM asset_software_subscription
|
||||
`);
|
||||
console.log(`📡 [GET /api/asset/software/subscription] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '구독SW' })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/software/subscription', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/software/perpetual', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
id, category AS \`분야\`, corp AS \`법인\`, dept AS \`부서\`, product_name AS \`제품명\`,
|
||||
quantity AS \`수량\`, price AS \`금액\`, purchase_date AS \`구매일\`, start_date AS \`시작일\`,
|
||||
expiry_date AS \`만료일\`, vendor AS \`납품업체\`, remarks AS \`비고\`, license_key AS \`라이선스키\`, account_name AS \`계정명\`
|
||||
FROM asset_software_perpetual
|
||||
`);
|
||||
console.log(`📡 [GET /api/asset/software/perpetual] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '영구SW' })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/software/perpetual', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/cloud', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
id, platform_name AS \`플랫폼명\`, corp AS \`법인\`, dept AS \`부서\`, product_name AS \`제품명\`,
|
||||
account_name AS \`계정명\`, pay_method AS \`결제수단\`, pay_day AS \`결제일\`, card_num AS \`연결카드번호\`,
|
||||
monthly_cost AS \`당월청구액\`, remarks AS \`비고\`
|
||||
FROM asset_cloud
|
||||
`);
|
||||
console.log(`📡 [GET /api/asset/cloud] Returning ${rows.length} rows`);
|
||||
res.json(rows.map(r => ({ ...r, type: '클라우드' })));
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/cloud', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/domain', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`SELECT * FROM asset_domain`);
|
||||
res.json(rows);
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/domain', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/software/assignment', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
id, sw_id, corp AS \`법인\`, dept AS \`부서\`, position AS \`직위\`, user_name AS \`이름\`, usage_period AS \`사용기간\`, doc_name AS \`신청서명\`
|
||||
FROM asset_software_assignment
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/software/assignment', true); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/history', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
id, asset_id AS assetId, log_date AS \`date\`, log_user AS \`user\`, details, cost
|
||||
FROM asset_history
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (err) { handleError(res, err, 'GET /api/asset/history', true); }
|
||||
});
|
||||
|
||||
// --- POST Batch Routes ---
|
||||
|
||||
async function saveHwBatch(category, items, res) {
|
||||
/**
|
||||
* Generic Batch Saver for Asset Tables
|
||||
*/
|
||||
const saveAssetsBatch = async (tableName, items, res, context) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
// 1. 해당 카테고리 기존 데이터 삭제
|
||||
await connection.query('DELETE FROM asset_hardware WHERE asset_category = ?', [category]);
|
||||
|
||||
// 2. 새 데이터 삽입
|
||||
// Get valid columns for this table
|
||||
const [cols] = await connection.query(`DESCRIBE ${tableName}`);
|
||||
const validColumns = cols.map(c => c.Field);
|
||||
|
||||
// 1. Clear existing (or we could use UPSERT logic, but existing code used DELETE-INSERT pattern)
|
||||
await connection.query(`DELETE FROM ${tableName}`);
|
||||
|
||||
// 2. Insert new items
|
||||
for (const item of items) {
|
||||
const dbRow = mapObject(item, HW_REVERSE_MAP);
|
||||
dbRow.asset_category = category;
|
||||
if (!dbRow.id) dbRow.id = Math.random().toString(36).substring(2, 9);
|
||||
|
||||
// DB 컬럼에 없는 필드 제거
|
||||
const validColumns = ['id', 'asset_category', 'corp', 'asset_code', 'purchase_date', 'asset_type', 'usage_category', 'user_name', 'usage_description', 'dept', 'prev_org', 'location', 'manager_primary', 'manager_secondary', 'ip_address', 'remote_tool', 'server_id', 'server_pw', 'product_name', 'mainboard', 'os', 'cpu', 'ram', 'gpu', 'storage1', 'storage2', 'storage3', 'monitoring', 'price', 'vendor', 'remarks', 'storage_location', 'status'];
|
||||
const filteredRow = {};
|
||||
validColumns.forEach(col => { if (dbRow[col] !== undefined) filteredRow[col] = dbRow[col]; });
|
||||
|
||||
await connection.query('INSERT INTO asset_hardware SET ?', [filteredRow]);
|
||||
validColumns.forEach(col => {
|
||||
// Exclude auto-managed timestamps from manual insertion
|
||||
if (col === 'created_at' || col === 'updated_at') return;
|
||||
|
||||
if (item[col] !== undefined) filteredRow[col] = item[col];
|
||||
});
|
||||
|
||||
// Auto-generate ID if missing
|
||||
if (!filteredRow.id) filteredRow.id = Math.random().toString(36).substring(2, 9);
|
||||
|
||||
await connection.query(`INSERT INTO ${tableName} SET ?`, [filteredRow]);
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
res.json({ success: true, count: items.length });
|
||||
} catch (err) {
|
||||
await connection.rollback();
|
||||
handleError(res, err, `BATCH SAVE ${category}`);
|
||||
handleError(res, err, context);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
app.post('/api/pc/batch', (req, res) => saveHwBatch('개인PC', req.body, res));
|
||||
app.post('/api/server/batch', (req, res) => saveHwBatch('서버', req.body, res));
|
||||
app.post('/api/storage/batch', (req, res) => saveHwBatch('스토리지', req.body, res));
|
||||
app.post('/api/equip/batch', (req, res) => saveHwBatch('전산비품', req.body, res));
|
||||
app.post('/api/mobile/batch', (req, res) => saveHwBatch('모바일기기', req.body, res));
|
||||
// --- Routes ---
|
||||
|
||||
app.post('/api/asset/software/subscription/batch', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_software_subscription');
|
||||
const mapping = { '분야': 'category', '법인': 'corp', '부서': 'dept', '제품명': 'product_name', '수량': 'quantity', '금액': 'price', '구매일': 'purchase_date', '시작일': 'start_date', '만료일': 'expiry_date', '납품업체': 'vendor', '비고': 'remarks', '라이선스유형': 'license_type', '계정명': 'account_name' };
|
||||
for (const item of req.body) {
|
||||
const dbRow = mapObject(item, mapping);
|
||||
if (!dbRow.id) dbRow.id = Math.random().toString(36).substring(2, 9);
|
||||
const filteredRow = {};
|
||||
['id', 'corp', 'category', 'dept', 'product_name', 'license_type', 'quantity', 'price', 'purchase_date', 'start_date', 'expiry_date', 'vendor', 'remarks', 'account_name'].forEach(c => { if (dbRow[c] !== undefined) filteredRow[c] = dbRow[c]; });
|
||||
await connection.query('INSERT INTO asset_software_subscription SET ?', [filteredRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH SW SUB'); } finally { connection.release(); }
|
||||
});
|
||||
// 0. User Management
|
||||
app.get('/api/users', (req, res) => fetchAssets('system_users', res, 'USERS'));
|
||||
app.post('/api/users/batch', (req, res) => saveAssetsBatch('system_users', req.body, res, 'USERS BATCH'));
|
||||
|
||||
app.post('/api/asset/software/perpetual/batch', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_software_perpetual');
|
||||
const mapping = { '분야': 'category', '법인': 'corp', '부서': 'dept', '제품명': 'product_name', '수량': 'quantity', '금액': 'price', '구매일': 'purchase_date', '시작일': 'start_date', '만료일': 'expiry_date', '납품업체': 'vendor', '비고': 'remarks', '라이선스키': 'license_key', '계정명': 'account_name' };
|
||||
for (const item of req.body) {
|
||||
const dbRow = mapObject(item, mapping);
|
||||
if (!dbRow.id) dbRow.id = Math.random().toString(36).substring(2, 9);
|
||||
const filteredRow = {};
|
||||
['id', 'corp', 'category', 'dept', 'product_name', 'license_key', 'quantity', 'price', 'purchase_date', 'start_date', 'expiry_date', 'vendor', 'remarks', 'account_name'].forEach(c => { if (dbRow[c] !== undefined) filteredRow[c] = dbRow[c]; });
|
||||
await connection.query('INSERT INTO asset_software_perpetual SET ?', [filteredRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH SW PERM'); } finally { connection.release(); }
|
||||
});
|
||||
// 1. Hardware Assets
|
||||
app.get('/api/pc', (req, res) => fetchAssets('asset_pc', res, 'PC'));
|
||||
app.post('/api/pc/batch', (req, res) => saveAssetsBatch('asset_pc', req.body, res, 'PC BATCH'));
|
||||
|
||||
app.post('/api/asset/cloud/batch', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_cloud');
|
||||
const mapping = { '플랫폼명': 'platform_name', '법인': 'corp', '부서': 'dept', '제품명': 'product_name', '계정명': 'account_name', '결제수단': 'pay_method', '결제일': 'pay_day', '연결카드번호': 'card_num', '당월청구액': 'monthly_cost', '비고': 'remarks' };
|
||||
for (const item of req.body) {
|
||||
const dbRow = mapObject(item, mapping);
|
||||
if (!dbRow.id) dbRow.id = Math.random().toString(36).substring(2, 9);
|
||||
const filteredRow = {};
|
||||
['id', 'platform_name', 'corp', 'dept', 'product_name', 'account_name', 'pay_method', 'pay_day', 'card_num', 'monthly_cost', 'remarks'].forEach(c => { if (dbRow[c] !== undefined) filteredRow[c] = dbRow[c]; });
|
||||
await connection.query('INSERT INTO asset_cloud SET ?', [filteredRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH CLOUD'); } finally { connection.release(); }
|
||||
});
|
||||
app.get('/api/server', (req, res) => fetchAssets('asset_server', res, 'SERVER'));
|
||||
app.post('/api/server/batch', (req, res) => saveAssetsBatch('asset_server', req.body, res, 'SERVER BATCH'));
|
||||
|
||||
app.post('/api/asset/software/assignment/batch', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_software_assignment');
|
||||
const mapping = { '법인': 'corp', '부서': 'dept', '직위': 'position', '이름': 'user_name', '사용기간': 'usage_period', '신청서명': 'doc_name' };
|
||||
for (const item of req.body) {
|
||||
const dbRow = mapObject(item, mapping);
|
||||
delete dbRow.id; // Auto-increment
|
||||
await connection.query('INSERT INTO asset_software_assignment SET ?', [dbRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH SW ASSIGN'); } finally { connection.release(); }
|
||||
});
|
||||
app.get('/api/storage', (req, res) => fetchAssets('asset_storage', res, 'STORAGE'));
|
||||
app.post('/api/storage/batch', (req, res) => saveAssetsBatch('asset_storage', req.body, res, 'STORAGE BATCH'));
|
||||
|
||||
app.get('/api/network', (req, res) => fetchAssets('asset_network', res, 'NETWORK'));
|
||||
app.post('/api/network/batch', (req, res) => saveAssetsBatch('asset_network', req.body, res, 'NETWORK BATCH'));
|
||||
|
||||
// 2. Software Assets
|
||||
app.get('/api/sw/internal', (req, res) => fetchAssets('asset_sw_internal', res, 'SW INTERNAL'));
|
||||
app.post('/api/sw/internal/batch', (req, res) => saveAssetsBatch('asset_sw_internal', req.body, res, 'SW INTERNAL BATCH'));
|
||||
|
||||
app.get('/api/sw/external', (req, res) => fetchAssets('asset_sw_external', res, 'SW EXTERNAL'));
|
||||
app.post('/api/sw/external/batch', (req, res) => saveAssetsBatch('asset_sw_external', req.body, res, 'SW EXTERNAL BATCH'));
|
||||
|
||||
// 3. Other Assets
|
||||
app.get('/api/survey', (req, res) => fetchAssets('asset_survey', res, 'SURVEY'));
|
||||
app.post('/api/survey/batch', (req, res) => saveAssetsBatch('asset_survey', req.body, res, 'SURVEY BATCH'));
|
||||
|
||||
app.get('/api/pc-parts', (req, res) => fetchAssets('asset_pc_parts', res, 'PC PARTS'));
|
||||
app.post('/api/pc-parts/batch', (req, res) => saveAssetsBatch('asset_pc_parts', req.body, res, 'PC PARTS BATCH'));
|
||||
|
||||
app.get('/api/equipment', (req, res) => fetchAssets('asset_equipment', res, 'EQUIPMENT'));
|
||||
app.post('/api/equipment/batch', (req, res) => saveAssetsBatch('asset_equipment', req.body, res, 'EQUIPMENT BATCH'));
|
||||
|
||||
app.get('/api/office-supplies', (req, res) => fetchAssets('asset_office_supplies', res, 'OFFICE SUPPLIES'));
|
||||
app.post('/api/office-supplies/batch', (req, res) => saveAssetsBatch('asset_office_supplies', req.body, res, 'OFFICE SUPPLIES BATCH'));
|
||||
|
||||
app.get('/api/cloud', (req, res) => fetchAssets('asset_cloud', res, 'CLOUD'));
|
||||
app.post('/api/cloud/batch', (req, res) => saveAssetsBatch('asset_cloud', req.body, res, 'CLOUD BATCH'));
|
||||
|
||||
app.get('/api/domain', (req, res) => fetchAssets('asset_domain', res, 'DOMAIN'));
|
||||
app.post('/api/domain/batch', (req, res) => saveAssetsBatch('asset_domain', req.body, res, 'DOMAIN BATCH'));
|
||||
|
||||
app.get('/api/cost', (req, res) => fetchAssets('asset_cost', res, 'COST'));
|
||||
app.post('/api/cost/batch', (req, res) => saveAssetsBatch('asset_cost', req.body, res, 'COST BATCH'));
|
||||
|
||||
app.get('/api/vip', (req, res) => fetchAssets('asset_vip', res, 'VIP'));
|
||||
app.post('/api/vip/batch', (req, res) => saveAssetsBatch('asset_vip', req.body, res, 'VIP BATCH'));
|
||||
|
||||
// 4. Legacy/Auxiliary (History & Assignment)
|
||||
app.get('/api/asset/history', (req, res) => fetchAssets('asset_history', res, 'HISTORY'));
|
||||
app.post('/api/asset/history/batch', async (req, res) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_history');
|
||||
for (const item of req.body) {
|
||||
const dbRow = {
|
||||
asset_id: item.assetId,
|
||||
log_date: item.date,
|
||||
log_user: item.user,
|
||||
details: item.details,
|
||||
cost: item.cost || 0
|
||||
};
|
||||
await connection.query('INSERT INTO asset_history SET ?', [dbRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH HISTORY'); } finally { connection.release(); }
|
||||
// Custom logic for history as it might not follow the random-id pattern
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM asset_history');
|
||||
for (const item of req.body) {
|
||||
const dbRow = {
|
||||
asset_id: item.assetId,
|
||||
log_date: item.date,
|
||||
log_user: item.user,
|
||||
details: item.details,
|
||||
cost: item.cost || 0
|
||||
};
|
||||
await connection.query('INSERT INTO asset_history SET ?', [dbRow]);
|
||||
}
|
||||
await connection.commit();
|
||||
res.json({ success: true });
|
||||
} catch (err) { await connection.rollback(); handleError(res, err, 'BATCH HISTORY'); } finally { connection.release(); }
|
||||
});
|
||||
|
||||
app.get('/api/asset/software/assignment', (req, res) => fetchAssets('asset_software_assignment', res, 'SW ASSIGN'));
|
||||
app.post('/api/asset/software/assignment/batch', (req, res) => saveAssetsBatch('asset_software_assignment', req.body, res, 'SW ASSIGN BATCH'));
|
||||
|
||||
// 5. Utility
|
||||
app.get('/api/generate-asset-code', async (req, res) => {
|
||||
try {
|
||||
const { prefix } = req.query;
|
||||
if (!prefix) return res.status(400).json({ error: 'Prefix is required' });
|
||||
const [rows] = await pool.query('SELECT asset_code FROM asset_hardware WHERE asset_code LIKE ? ORDER BY asset_code DESC LIMIT 1', [`${prefix}%`]);
|
||||
|
||||
// Search in multiple tables if necessary, but typically prefix-based tables are known
|
||||
const tables = ['asset_pc', 'asset_server', 'asset_storage', 'asset_network', 'asset_survey', 'asset_pc_parts', 'asset_equipment', 'asset_office_supplies', 'asset_vip'];
|
||||
let lastCode = '';
|
||||
|
||||
for (const table of tables) {
|
||||
const [rows] = await pool.query(`SELECT asset_code FROM ${table} WHERE asset_code LIKE ? ORDER BY asset_code DESC LIMIT 1`, [`${prefix}%`]);
|
||||
if (rows.length > 0 && rows[0].asset_code > lastCode) {
|
||||
lastCode = rows[0].asset_code;
|
||||
}
|
||||
}
|
||||
|
||||
let nextNum = 1;
|
||||
if (rows.length > 0) {
|
||||
const lastCode = rows[0].asset_code;
|
||||
if (lastCode) {
|
||||
const lastNum = parseInt(lastCode.split('-').pop() || '0');
|
||||
nextNum = lastNum + 1;
|
||||
}
|
||||
@@ -367,5 +190,5 @@ app.get('/api/generate-asset-code', async (req, res) => {
|
||||
});
|
||||
|
||||
app.listen(3000, '0.0.0.0', () => {
|
||||
console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (Safe Korean Mapping)');
|
||||
console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (Multi-Table Optimized)');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user