Fix: Worker/Project status update and filtering issues
- Added cache invalidation for Workers and Projects - Implemented server-side status filtering for Workers - Fixed worker update query bug (removed non-existent join_date column) - Updated daily-work-report UI to fetch only active workers
This commit is contained in:
14
DEV_LOG.md
14
DEV_LOG.md
@@ -27,6 +27,20 @@
|
||||
4. **테스트 계정 생성**
|
||||
- `tester` / `000000` 관리자(Leader) 계정 생성.
|
||||
|
||||
### 🛠️ 작업자 및 프로젝트 관리 기능 개선 (2026-01-06)
|
||||
**개요**: 작업자/프로젝트의 비활성화(퇴사/종료) 처리가 즉시 반영되지 않는 문제 및 로직 오류 수정.
|
||||
|
||||
1. **캐시 무효화 및 필터링 적용 (Cache & Filtering)**
|
||||
- **문제**: 작업자/프로젝트 상태 변경 후에도 캐시가 남아있어 드롭다운 목록에서 사라지지 않음.
|
||||
- **해결**:
|
||||
- `WorkerController`, `ProjectController`: 생성/수정/삭제 시 `request` 단위의 캐시 즉시 무효화 로직 추가.
|
||||
- `WorkerController`: 목록 조회 시 `status` 파라미터 지원 추가.
|
||||
- `daily-work-report.js`: 작업보고서 작성 시 `active` 상태인 작업자만 필터링하여 조회하도록 수정.
|
||||
|
||||
2. **작업자 비활성화 오류 수정 (Bug Fix)**
|
||||
- **원인**: `workerModel.update` 쿼리에 DB에 존재하지 않는 `join_date` 컬럼을 업데이트하려는 시도가 있어 SQL 에러 발생.
|
||||
- **해결**: `workerModel.js`에서 잘못된 컬럼(`join_date`) 참조 제거. (올바른 컬럼 `hire_date`는 유지)
|
||||
|
||||
---
|
||||
|
||||
## 🛡보안 및 검토 리포트 (History)
|
||||
|
||||
@@ -11,6 +11,7 @@ const projectModel = require('../models/projectModel');
|
||||
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
const cache = require('../utils/cache');
|
||||
|
||||
/**
|
||||
* 프로젝트 생성
|
||||
@@ -27,6 +28,9 @@ exports.createProject = asyncHandler(async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 프로젝트 캐시 무효화
|
||||
await cache.invalidateCache.project();
|
||||
|
||||
logger.info('프로젝트 생성 성공', { project_id: id });
|
||||
|
||||
res.status(201).json({
|
||||
@@ -123,6 +127,9 @@ exports.updateProject = asyncHandler(async (req, res) => {
|
||||
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 프로젝트 캐시 무효화
|
||||
await cache.invalidateCache.project();
|
||||
|
||||
logger.info('프로젝트 수정 성공', { project_id: id });
|
||||
|
||||
res.json({
|
||||
@@ -153,6 +160,9 @@ exports.removeProject = asyncHandler(async (req, res) => {
|
||||
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 프로젝트 캐시 무효화
|
||||
await cache.invalidateCache.project();
|
||||
|
||||
logger.info('프로젝트 삭제 성공', { project_id: id });
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -45,9 +45,9 @@ exports.createWorker = asyncHandler(async (req, res) => {
|
||||
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
|
||||
*/
|
||||
exports.getAllWorkers = asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10, search = '' } = req.query;
|
||||
const { page = 1, limit = 10, search = '', status = '' } = req.query;
|
||||
|
||||
const cacheKey = cache.createKey('workers', 'list', page, limit, search);
|
||||
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status);
|
||||
|
||||
// 캐시에서 조회
|
||||
const cachedData = await cache.get(cacheKey);
|
||||
@@ -62,7 +62,7 @@ exports.getAllWorkers = asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
// 최적화된 쿼리 사용
|
||||
const result = await optimizedQueries.getWorkersPaged(page, limit, search);
|
||||
const result = await optimizedQueries.getWorkersPaged(page, limit, search, status);
|
||||
|
||||
// 캐시에 저장 (5분)
|
||||
await cache.set(cacheKey, result, cache.TTL.MEDIUM);
|
||||
@@ -127,6 +127,10 @@ exports.updateWorker = asyncHandler(async (req, res) => {
|
||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// 작업자 관련 캐시 무효화
|
||||
logger.info('작업자 수정 후 캐시 무효화', { worker_id: id });
|
||||
await cache.invalidateCache.worker();
|
||||
|
||||
logger.info('작업자 수정 성공', { worker_id: id });
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -79,7 +79,6 @@ const update = async (worker, callback) => {
|
||||
worker_id,
|
||||
worker_name,
|
||||
job_type,
|
||||
join_date,
|
||||
status,
|
||||
phone_number,
|
||||
email,
|
||||
@@ -92,7 +91,6 @@ const update = async (worker, callback) => {
|
||||
`UPDATE workers
|
||||
SET worker_name = ?,
|
||||
job_type = ?,
|
||||
join_date = ?,
|
||||
status = ?,
|
||||
phone_number = ?,
|
||||
email = ?,
|
||||
@@ -103,7 +101,6 @@ const update = async (worker, callback) => {
|
||||
[
|
||||
worker_name,
|
||||
job_type,
|
||||
formatDate(join_date),
|
||||
status,
|
||||
phone_number,
|
||||
email,
|
||||
|
||||
@@ -234,7 +234,7 @@ const generateCacheKey = (query, params = [], prefix = 'query') => {
|
||||
*/
|
||||
const optimizedQueries = {
|
||||
// 작업자 목록 (페이지네이션)
|
||||
getWorkersPaged: async (page = 1, limit = 10, search = '') => {
|
||||
getWorkersPaged: async (page = 1, limit = 10, search = '', status = '') => {
|
||||
let baseQuery = `
|
||||
SELECT w.*, COUNT(dwr.id) as report_count
|
||||
FROM workers w
|
||||
@@ -243,16 +243,29 @@ const optimizedQueries = {
|
||||
|
||||
let countQuery = 'SELECT COUNT(*) as total FROM workers w';
|
||||
let params = [];
|
||||
let conditions = [];
|
||||
|
||||
// 검색 조건
|
||||
if (search) {
|
||||
const searchCondition = ' WHERE w.worker_name LIKE ? OR w.position LIKE ?';
|
||||
baseQuery += searchCondition + ' GROUP BY w.worker_id';
|
||||
countQuery += searchCondition;
|
||||
params = [`%${search}%`, `%${search}%`];
|
||||
} else {
|
||||
baseQuery += ' GROUP BY w.worker_id';
|
||||
conditions.push('(w.worker_name LIKE ? OR w.position LIKE ?)');
|
||||
params.push(`%${search}%`, `%${search}%`);
|
||||
}
|
||||
|
||||
// 상태 조건
|
||||
if (status) {
|
||||
conditions.push('w.status = ?');
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
// 조건 조합
|
||||
if (conditions.length > 0) {
|
||||
const whereClause = ' WHERE ' + conditions.join(' AND ');
|
||||
baseQuery += whereClause;
|
||||
countQuery += whereClause;
|
||||
}
|
||||
|
||||
baseQuery += ' GROUP BY w.worker_id';
|
||||
|
||||
return executePagedQuery(baseQuery, countQuery, params, {
|
||||
page, limit, orderBy: 'w.worker_id', orderDirection: 'DESC'
|
||||
});
|
||||
|
||||
@@ -117,14 +117,14 @@ function showSaveResultModal(type, title, message, details = null) {
|
||||
modal.style.display = 'flex';
|
||||
|
||||
// ESC 키로 닫기
|
||||
document.addEventListener('keydown', function(e) {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeSaveResultModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 배경 클릭으로 닫기
|
||||
modal.addEventListener('click', function(e) {
|
||||
modal.addEventListener('click', function (e) {
|
||||
if (e.target === modal) {
|
||||
closeSaveResultModal();
|
||||
}
|
||||
@@ -206,7 +206,8 @@ async function loadData() {
|
||||
async function loadWorkers() {
|
||||
try {
|
||||
console.log('Workers API 호출 중... (통합 API 사용)');
|
||||
const data = await window.apiCall(`${window.API}/workers`);
|
||||
// 활성 작업자 1000명까지 조회 (서버 사이드 필터링 적용)
|
||||
const data = await window.apiCall(`${window.API}/workers?status=active&limit=1000`);
|
||||
const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []);
|
||||
|
||||
// 활성화된 작업자만 필터링
|
||||
@@ -245,9 +246,9 @@ async function loadWorkTypes() {
|
||||
} catch (error) {
|
||||
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용');
|
||||
workTypes = [
|
||||
{id: 1, name: 'Base'},
|
||||
{id: 2, name: 'Vessel'},
|
||||
{id: 3, name: 'Piping'}
|
||||
{ id: 1, name: 'Base' },
|
||||
{ id: 2, name: 'Vessel' },
|
||||
{ id: 3, name: 'Piping' }
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -264,8 +265,8 @@ async function loadWorkStatusTypes() {
|
||||
} catch (error) {
|
||||
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
||||
workStatusTypes = [
|
||||
{id: 1, name: '정규'},
|
||||
{id: 2, name: '에러'}
|
||||
{ id: 1, name: '정규' },
|
||||
{ id: 2, name: '에러' }
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -282,10 +283,10 @@ async function loadErrorTypes() {
|
||||
} catch (error) {
|
||||
console.log('⚠️ 에러 유형 API 사용 불가, 기본값 사용');
|
||||
errorTypes = [
|
||||
{id: 1, name: '설계미스'},
|
||||
{id: 2, name: '외주작업 불량'},
|
||||
{id: 3, name: '입고지연'},
|
||||
{id: 4, name: '작업 불량'}
|
||||
{ id: 1, name: '설계미스' },
|
||||
{ id: 2, name: '외주작업 불량' },
|
||||
{ id: 3, name: '입고지연' },
|
||||
{ id: 4, name: '작업 불량' }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user