/** * 작업자 관리 컨트롤러 * * 작업자 CRUD API 엔드포인트 핸들러 * * @author TK-FB-Project * @since 2025-12-11 */ const workerModel = require('../models/workerModel'); const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); const { asyncHandler } = require('../middlewares/errorHandler'); const logger = require('../utils/logger'); const cache = require('../utils/cache'); const { optimizedQueries } = require('../utils/queryOptimizer'); const { hangulToRoman, generateUniqueUsername } = require('../utils/hangulToRoman'); const bcrypt = require('bcrypt'); const { getDb } = require('../dbPool'); /** * 작업자 생성 */ exports.createWorker = asyncHandler(async (req, res) => { const workerData = req.body; const createAccount = req.body.create_account; logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount }); const lastID = await workerModel.create(workerData); // 계정 생성 요청이 있으면 users 테이블에 계정 생성 if (createAccount && workerData.worker_name) { try { const db = await getDb(); const username = await generateUniqueUsername(workerData.worker_name, db); const hashedPassword = await bcrypt.hash('1234', 10); // User 역할 조회 const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']); if (userRole && userRole.length > 0) { await db.query( `INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())`, [username, hashedPassword, workerData.worker_name, lastID, userRole[0].id] ); logger.info('작업자 계정 자동 생성 성공', { worker_id: lastID, username }); } } catch (accountError) { logger.error('계정 생성 실패 (작업자는 생성됨)', { worker_id: lastID, error: accountError.message }); } } // 작업자 관련 캐시 무효화 await cache.invalidateCache.worker(); logger.info('작업자 생성 성공', { worker_id: lastID }); res.status(201).json({ success: true, data: { worker_id: lastID }, message: '작업자가 성공적으로 생성되었습니다' }); }); /** * 전체 작업자 조회 (캐싱 및 페이지네이션 적용) */ exports.getAllWorkers = asyncHandler(async (req, res) => { const { page = 1, limit = 100, search = '', status = '', department_id = null } = req.query; const cacheKey = cache.createKey('workers', 'list', page, limit, search, status, department_id); // 캐시에서 조회 const cachedData = await cache.get(cacheKey); if (cachedData) { logger.debug('캐시 히트', { cacheKey }); return res.json({ success: true, data: cachedData.data, pagination: cachedData.pagination, message: '작업자 목록 조회 성공 (캐시)' }); } // 최적화된 쿼리 사용 const result = await optimizedQueries.getWorkersPaged(page, limit, search, status, department_id); // 캐시에 저장 (5분) await cache.set(cacheKey, result, cache.TTL.MEDIUM); logger.debug('캐시 저장', { cacheKey }); res.json({ success: true, data: result.data, pagination: result.pagination, message: '작업자 목록 조회 성공' }); }); /** * 단일 작업자 조회 */ exports.getWorkerById = asyncHandler(async (req, res) => { const id = parseInt(req.params.worker_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); } const row = await workerModel.getById(id); if (!row) { throw new NotFoundError('작업자를 찾을 수 없습니다'); } res.json({ success: true, data: row, message: '작업자 조회 성공' }); }); /** * 작업자 수정 */ exports.updateWorker = asyncHandler(async (req, res) => { const id = parseInt(req.params.worker_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); } const workerData = { ...req.body, worker_id: id }; const createAccount = req.body.create_account; console.log('🔧 작업자 수정 요청:', { worker_id: id, 받은데이터: req.body, 처리할데이터: workerData, create_account: createAccount }); // 먼저 현재 작업자 정보 조회 (계정 여부 확인용) const currentWorker = await workerModel.getById(id); if (!currentWorker) { throw new NotFoundError('작업자를 찾을 수 없습니다'); } // 작업자 정보 업데이트 const changes = await workerModel.update(workerData); // 계정 생성/해제 처리 const db = await getDb(); const hasAccount = currentWorker.user_id !== null && currentWorker.user_id !== undefined; let accountAction = null; let accountUsername = null; console.log('🔍 계정 생성 체크:', { createAccount, hasAccount, currentWorker_user_id: currentWorker.user_id, worker_name: workerData.worker_name }); if (createAccount && !hasAccount && workerData.worker_name) { // 계정 생성 console.log('✅ 계정 생성 로직 시작'); try { console.log('🔑 사용자명 생성 중...'); const username = await generateUniqueUsername(workerData.worker_name, db); console.log('🔑 생성된 사용자명:', username); const hashedPassword = await bcrypt.hash('1234', 10); console.log('🔒 비밀번호 해싱 완료'); // User 역할 조회 console.log('👤 User 역할 조회 중...'); const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']); console.log('👤 User 역할 조회 결과:', userRole); if (userRole && userRole.length > 0) { console.log('💾 계정 DB 삽입 시작...'); await db.query( `INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())`, [username, hashedPassword, workerData.worker_name, id, userRole[0].id] ); console.log('✅ 계정 DB 삽입 완료'); accountAction = 'created'; accountUsername = username; logger.info('작업자 계정 생성 성공', { worker_id: id, username }); } else { console.log('❌ User 역할을 찾을 수 없음'); } } catch (accountError) { console.error('❌ 계정 생성 오류:', accountError); logger.error('계정 생성 실패', { worker_id: id, error: accountError.message }); accountAction = 'failed'; } } else { console.log('⏭️ 계정 생성 조건 불만족:', { createAccount, hasAccount, hasWorkerName: !!workerData.worker_name }); } if (!createAccount && hasAccount) { // 계정 연동 해제 (users.worker_id = NULL) try { await db.query('UPDATE users SET worker_id = NULL WHERE worker_id = ?', [id]); accountAction = 'unlinked'; logger.info('작업자 계정 연동 해제 성공', { worker_id: id }); } catch (unlinkError) { logger.error('계정 연동 해제 실패', { worker_id: id, error: unlinkError.message }); accountAction = 'unlink_failed'; } } else if (createAccount && hasAccount) { accountAction = 'already_exists'; } // 작업자 관련 캐시 무효화 logger.info('작업자 수정 후 캐시 무효화', { worker_id: id }); await cache.invalidateCache.worker(); logger.info('작업자 수정 성공', { worker_id: id }); // 응답 메시지 구성 let message = '작업자 정보가 성공적으로 수정되었습니다'; if (accountAction === 'created') { message += ` (계정 생성 완료: ${accountUsername}, 초기 비밀번호: 1234)`; } else if (accountAction === 'unlinked') { message += ' (계정 연동 해제 완료)'; } else if (accountAction === 'already_exists') { message += ' (이미 계정이 존재합니다)'; } else if (accountAction === 'failed') { message += ' (계정 생성 실패)'; } res.json({ success: true, data: { changes, account_action: accountAction, account_username: accountUsername }, message }); }); /** * 작업자 삭제 */ exports.removeWorker = asyncHandler(async (req, res) => { const id = parseInt(req.params.worker_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); } const changes = await workerModel.remove(id); if (changes === 0) { throw new NotFoundError('작업자를 찾을 수 없습니다'); } // 작업자 관련 캐시 무효화 logger.info('작업자 삭제 후 캐시 무효화 시작', { worker_id: id }); await cache.invalidateCache.worker(); await cache.delPattern('workers:*'); await cache.flush(); logger.info('작업자 삭제 후 캐시 무효화 완료', { worker_id: id }); res.json({ success: true, message: '작업자가 성공적으로 삭제되었습니다' }); });