- workerModel: remove()를 user_id 기반 cascading delete로 전환 - workerController: 계정 생성/해제를 workers.user_id 연결 방식으로 변경 - userController: JOIN 방향 전환 (u.worker_id→w.worker_id 에서 w.user_id→u.user_id) - authController, systemController, authRoutes: 모든 CRUD에서 worker_id 참조 제거 - DB: UNIQUE KEY 5개 교체, FK 7개 삭제, daily_worker_summary user_id 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
264 lines
8.3 KiB
JavaScript
264 lines
8.3 KiB
JavaScript
/**
|
|
* 작업자 관리 컨트롤러
|
|
*
|
|
* 작업자 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('bcryptjs');
|
|
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 테이블에 계정 생성 + workers.user_id 연결
|
|
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) {
|
|
const [insertResult] = await db.query(
|
|
`INSERT INTO users (username, password, name, role_id, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, NOW(), NOW())`,
|
|
[username, hashedPassword, workerData.worker_name, userRole[0].id]
|
|
);
|
|
|
|
// workers.user_id 연결
|
|
const newUserId = insertResult.insertId;
|
|
await db.query('UPDATE workers SET user_id = ? WHERE worker_id = ?', [newUserId, lastID]);
|
|
|
|
logger.info('작업자 계정 자동 생성 성공', { worker_id: lastID, user_id: newUserId, 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.user_id, 10);
|
|
|
|
if (isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
|
}
|
|
|
|
const row = await workerModel.getByUserId(id);
|
|
|
|
if (!row) {
|
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: row,
|
|
message: '작업자 조회 성공'
|
|
});
|
|
});
|
|
|
|
/**
|
|
* 작업자 수정
|
|
*/
|
|
exports.updateWorker = asyncHandler(async (req, res) => {
|
|
const id = parseInt(req.params.user_id, 10);
|
|
|
|
if (isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
|
}
|
|
|
|
const workerData = { ...req.body, user_id: id };
|
|
const createAccount = req.body.create_account;
|
|
|
|
console.log('🔧 작업자 수정 요청:', {
|
|
user_id: id,
|
|
받은데이터: req.body,
|
|
처리할데이터: workerData,
|
|
create_account: createAccount
|
|
});
|
|
|
|
// 먼저 현재 작업자 정보 조회 (계정 여부 확인용, user_id 기준)
|
|
const currentWorker = await workerModel.getByUserId(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;
|
|
|
|
if (createAccount && !hasAccount && workerData.worker_name) {
|
|
// 계정 생성
|
|
try {
|
|
const username = await generateUniqueUsername(workerData.worker_name, db);
|
|
const hashedPassword = await bcrypt.hash('1234', 10);
|
|
|
|
const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']);
|
|
|
|
if (userRole && userRole.length > 0) {
|
|
const [insertResult] = await db.query(
|
|
`INSERT INTO users (username, password, name, role_id, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, NOW(), NOW())`,
|
|
[username, hashedPassword, workerData.worker_name, userRole[0].id]
|
|
);
|
|
|
|
// workers.user_id 연결
|
|
const newUserId = insertResult.insertId;
|
|
await db.query('UPDATE workers SET user_id = ? WHERE user_id = ?', [newUserId, id]);
|
|
|
|
accountAction = 'created';
|
|
accountUsername = username;
|
|
logger.info('작업자 계정 생성 성공', { user_id: id, new_user_id: newUserId, username });
|
|
}
|
|
} catch (accountError) {
|
|
logger.error('계정 생성 실패', { user_id: id, error: accountError.message });
|
|
accountAction = 'failed';
|
|
}
|
|
}
|
|
|
|
if (!createAccount && hasAccount) {
|
|
// 계정 연동 해제 (workers.user_id = NULL)
|
|
try {
|
|
await db.query('UPDATE workers SET user_id = NULL WHERE user_id = ?', [id]);
|
|
accountAction = 'unlinked';
|
|
logger.info('작업자 계정 연동 해제 성공', { user_id: id });
|
|
} catch (unlinkError) {
|
|
logger.error('계정 연동 해제 실패', { user_id: id, error: unlinkError.message });
|
|
accountAction = 'unlink_failed';
|
|
}
|
|
} else if (createAccount && hasAccount) {
|
|
accountAction = 'already_exists';
|
|
}
|
|
|
|
// 작업자 관련 캐시 무효화
|
|
logger.info('작업자 수정 후 캐시 무효화', { user_id: id });
|
|
await cache.invalidateCache.worker();
|
|
|
|
logger.info('작업자 수정 성공', { user_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.user_id, 10);
|
|
|
|
if (isNaN(id)) {
|
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
|
}
|
|
|
|
const changes = await workerModel.remove(id);
|
|
|
|
if (changes === 0) {
|
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
|
}
|
|
|
|
// 작업자 관련 캐시 무효화
|
|
logger.info('작업자 삭제 후 캐시 무효화 시작', { user_id: id });
|
|
await cache.invalidateCache.worker();
|
|
await cache.delPattern('workers:*');
|
|
await cache.flush();
|
|
logger.info('작업자 삭제 후 캐시 무효화 완료', { user_id: id });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '작업자가 성공적으로 삭제되었습니다'
|
|
});
|
|
}); |