diff --git a/api.hyungi.net/controllers/workerController.js b/api.hyungi.net/controllers/workerController.js index c8bdce5..9c8b20b 100644 --- a/api.hyungi.net/controllers/workerController.js +++ b/api.hyungi.net/controllers/workerController.js @@ -13,14 +13,18 @@ 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.name }); + logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount }); const lastID = await new Promise((resolve, reject) => { workerModel.create(workerData, (err, id) => { @@ -29,6 +33,30 @@ exports.createWorker = asyncHandler(async (req, res) => { }); }); + // 계정 생성 요청이 있으면 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(); @@ -115,13 +143,28 @@ exports.updateWorker = asyncHandler(async (req, res) => { } const workerData = { ...req.body, worker_id: id }; + const createAccount = req.body.create_account; console.log('🔧 작업자 수정 요청:', { worker_id: id, 받은데이터: req.body, - 처리할데이터: workerData + 처리할데이터: 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) { @@ -132,8 +175,39 @@ exports.updateWorker = asyncHandler(async (req, res) => { }); }); - if (changes === 0) { - throw new NotFoundError('작업자를 찾을 수 없습니다'); + // 계정 생성/해제 처리 + 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 }); + } } // 작업자 관련 캐시 무효화 diff --git a/api.hyungi.net/db/migrations/20260119095549_add_worker_display_fields.js b/api.hyungi.net/db/migrations/20260119095549_add_worker_display_fields.js index 3a52b9e..4cb664d 100644 --- a/api.hyungi.net/db/migrations/20260119095549_add_worker_display_fields.js +++ b/api.hyungi.net/db/migrations/20260119095549_add_worker_display_fields.js @@ -4,20 +4,14 @@ */ exports.up = async function(knex) { await knex.schema.alterTable('workers', (table) => { - // 작업 보고서 표시 여부 (기본값: true, 작업자는 표시, 관리자는 선택 가능) - table.boolean('show_in_work_reports') - .defaultTo(true) - .notNullable() - .comment('작업 보고서에 표시 여부'); - // 재직 상태 (employed: 재직, resigned: 퇴사) table.enum('employment_status', ['employed', 'resigned']) .defaultTo('employed') .notNullable() - .comment('재직 상태'); + .comment('재직 상태 (employed: 재직, resigned: 퇴사)'); }); - console.log('✅ workers 테이블에 show_in_work_reports, employment_status 컬럼 추가 완료'); + console.log('✅ workers 테이블에 employment_status 컬럼 추가 완료'); }; /** @@ -26,9 +20,8 @@ exports.up = async function(knex) { */ exports.down = async function(knex) { await knex.schema.alterTable('workers', (table) => { - table.dropColumn('show_in_work_reports'); table.dropColumn('employment_status'); }); - console.log('✅ workers 테이블에서 show_in_work_reports, employment_status 컬럼 삭제 완료'); + console.log('✅ workers 테이블에서 employment_status 컬럼 삭제 완료'); }; diff --git a/api.hyungi.net/models/workerModel.js b/api.hyungi.net/models/workerModel.js index f2ef1ab..c48c27d 100644 --- a/api.hyungi.net/models/workerModel.js +++ b/api.hyungi.net/models/workerModel.js @@ -20,15 +20,14 @@ const create = async (worker, callback) => { salary = null, annual_leave = null, status = 'active', - show_in_work_reports = true, employment_status = 'employed' } = worker; const [result] = await db.query( `INSERT INTO workers - (worker_name, job_type, join_date, salary, annual_leave, status, show_in_work_reports, employment_status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - [worker_name, job_type, formatDate(join_date), salary, annual_leave, status, show_in_work_reports, employment_status] + (worker_name, job_type, join_date, salary, annual_leave, status, employment_status) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status] ); callback(null, result.insertId); @@ -44,10 +43,12 @@ const getAll = async (callback) => { const db = await getDb(); const [rows] = await db.query(` SELECT - *, - CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active - FROM workers - ORDER BY worker_id DESC + w.*, + CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active, + u.user_id + FROM workers w + LEFT JOIN users u ON w.worker_id = u.worker_id + ORDER BY w.worker_id DESC `); callback(null, rows); } catch (err) { @@ -61,10 +62,12 @@ const getById = async (worker_id, callback) => { const db = await getDb(); const [rows] = await db.query(` SELECT - *, - CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active - FROM workers - WHERE worker_id = ? + w.*, + CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active, + u.user_id + FROM workers w + LEFT JOIN users u ON w.worker_id = u.worker_id + WHERE w.worker_id = ? `, [worker_id]); callback(null, rows[0]); } catch (err) { @@ -84,7 +87,6 @@ const update = async (worker, callback) => { join_date, salary, annual_leave, - show_in_work_reports, employment_status } = worker; @@ -116,10 +118,6 @@ const update = async (worker, callback) => { updates.push('annual_leave = ?'); values.push(annual_leave); } - if (show_in_work_reports !== undefined) { - updates.push('show_in_work_reports = ?'); - values.push(show_in_work_reports); - } if (employment_status !== undefined) { updates.push('employment_status = ?'); values.push(employment_status); diff --git a/web-ui/js/daily-work-report.js b/web-ui/js/daily-work-report.js index a91223f..5037e64 100644 --- a/web-ui/js/daily-work-report.js +++ b/web-ui/js/daily-work-report.js @@ -211,16 +211,14 @@ async function loadWorkers() { const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []); // 작업 보고서에 표시할 작업자만 필터링 - // 1. show_in_work_reports가 true - // 2. employment_status가 resigned가 아님 + // 퇴사자만 제외 (계정 여부와 무관하게 재직자는 모두 표시) workers = allWorkers.filter(worker => { - const showInReports = worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false; const notResigned = worker.employment_status !== 'resigned'; - return showInReports && notResigned; + return notResigned; }); console.log(`✅ Workers 로드 성공: ${workers.length}명 (전체: ${allWorkers.length}명)`); - console.log(`📊 필터링 조건: show_in_work_reports=true, employment_status≠resigned`); + console.log(`📊 필터링 조건: employment_status≠resigned (퇴사자만 제외)`); } catch (error) { console.error('작업자 로딩 오류:', error); throw error; diff --git a/web-ui/js/worker-management.js b/web-ui/js/worker-management.js index 781c0de..1e5d78b 100644 --- a/web-ui/js/worker-management.js +++ b/web-ui/js/worker-management.js @@ -198,7 +198,7 @@ function renderWorkers() { const jobType = jobTypeMap[worker.job_type] || jobTypeMap['worker']; const isInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false; const isResigned = worker.employment_status === 'resigned'; - const showInWorkReports = worker.show_in_work_reports !== 0 && worker.show_in_work_reports !== false; + const hasAccount = worker.user_id !== null && worker.user_id !== undefined; console.log('🎨 카드 렌더링:', { worker_id: worker.worker_id, @@ -207,7 +207,8 @@ function renderWorkers() { is_active: worker.is_active, isInactive: isInactive, isResigned: isResigned, - showInWorkReports: showInWorkReports + user_id: worker.user_id, + hasAccount: hasAccount }); return ` @@ -221,17 +222,18 @@ function renderWorkers() {