작업 보고서 표시 여부 대신 계정 연동 기능으로 개선했습니다.
## 주요 변경사항
### 개념 변경
- **이전**: 작업 보고서 표시 여부 (show_in_work_reports)
- **이후**: 계정 생성/연동 기능
### 데이터베이스
- **마이그레이션**: 20260119095549_add_worker_display_fields.js
- show_in_work_reports 컬럼 제거
- employment_status만 유지 (employed/resigned)
- **workerModel**:
- getAll, getById에서 users 테이블 JOIN하여 user_id 조회
- create, update에서 show_in_work_reports 필드 제거
### 백엔드 API
- **workerController.js**:
- createWorker: create_account 체크 시 자동으로 users 테이블에 계정 생성
- username: hangulToRoman으로 한글 이름 변환
- password: 초기 비밀번호 '1234' (bcrypt 해시)
- role: User 역할 자동 할당
- updateWorker:
- create_account=true & 계정 없음 → 계정 생성
- create_account=false & 계정 있음 → 계정 연동 해제 (users.worker_id=NULL)
### 프론트엔드
- **worker-management.html**:
- "작업 보고서 표시" → "🔐 계정 생성/연동"으로 변경
- 체크 시 로그인 계정 자동 생성 안내
- **worker-management.js**:
- 카드 렌더링: user_id 존재 여부로 계정 연동 상태 표시 (🔐 아이콘)
- saveWorker: create_account 필드 전송
- show_in_work_reports 관련 로직 모두 제거
- **daily-work-report.js**:
- 필터링 조건 단순화: 퇴사자만 제외 (employment_status≠resigned)
- 계정 여부와 무관하게 모든 재직자 표시
## 사용 방법
1. 작업자 등록/수정 시 "계정 생성/연동" 체크
2. 자동으로 로그인 계정 생성 (초기 비밀번호: 1234)
3. 계정이 있는 작업자는 나의 대시보드, 연차/출퇴근 관리 가능
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
258 lines
8.1 KiB
JavaScript
258 lines
8.1 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('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 new Promise((resolve, reject) => {
|
|
workerModel.create(workerData, (err, id) => {
|
|
if (err) reject(new DatabaseError('작업자 생성 중 오류가 발생했습니다'));
|
|
else resolve(id);
|
|
});
|
|
});
|
|
|
|
// 계정 생성 요청이 있으면 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 = 10, search = '', status = '' } = req.query;
|
|
|
|
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status);
|
|
|
|
// 캐시에서 조회
|
|
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);
|
|
|
|
// 캐시에 저장 (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 new Promise((resolve, reject) => {
|
|
workerModel.getById(id, (err, data) => {
|
|
if (err) reject(new DatabaseError('작업자 조회 중 오류가 발생했습니다'));
|
|
else resolve(data);
|
|
});
|
|
});
|
|
|
|
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 new Promise((resolve, reject) => {
|
|
workerModel.getById(id, (err, data) => {
|
|
if (err) reject(new DatabaseError('작업자 조회 중 오류가 발생했습니다'));
|
|
else resolve(data);
|
|
});
|
|
});
|
|
|
|
if (!currentWorker) {
|
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
|
}
|
|
|
|
// 작업자 정보 업데이트
|
|
const changes = await new Promise((resolve, reject) => {
|
|
workerModel.update(workerData, (err, affected) => {
|
|
if (err) {
|
|
console.error('❌ workerModel.update 에러:', err);
|
|
reject(new DatabaseError(`작업자 수정 중 오류가 발생했습니다: ${err.message}`));
|
|
}
|
|
else resolve(affected);
|
|
});
|
|
});
|
|
|
|
// 계정 생성/해제 처리
|
|
const db = await getDb();
|
|
const hasAccount = currentWorker.user_id !== null && currentWorker.user_id !== undefined;
|
|
|
|
if (createAccount && !hasAccount && workerData.worker_name) {
|
|
// 계정 생성
|
|
try {
|
|
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, id, userRole[0].id]
|
|
);
|
|
|
|
logger.info('작업자 계정 생성 성공', { worker_id: id, username });
|
|
}
|
|
} catch (accountError) {
|
|
logger.error('계정 생성 실패', { worker_id: id, error: accountError.message });
|
|
}
|
|
} else if (!createAccount && hasAccount) {
|
|
// 계정 연동 해제 (users.worker_id = NULL)
|
|
try {
|
|
await db.query('UPDATE users SET worker_id = NULL WHERE worker_id = ?', [id]);
|
|
logger.info('작업자 계정 연동 해제 성공', { worker_id: id });
|
|
} catch (unlinkError) {
|
|
logger.error('계정 연동 해제 실패', { worker_id: id, error: unlinkError.message });
|
|
}
|
|
}
|
|
|
|
// 작업자 관련 캐시 무효화
|
|
logger.info('작업자 수정 후 캐시 무효화', { worker_id: id });
|
|
await cache.invalidateCache.worker();
|
|
|
|
logger.info('작업자 수정 성공', { worker_id: id });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { changes },
|
|
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 new Promise((resolve, reject) => {
|
|
workerModel.remove(id, (err, affected) => {
|
|
if (err) reject(new DatabaseError('작업자 삭제 중 오류가 발생했습니다'));
|
|
else resolve(affected);
|
|
});
|
|
});
|
|
|
|
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: '작업자가 성공적으로 삭제되었습니다'
|
|
});
|
|
}); |