diff --git a/sso-auth-service/controllers/authController.js b/sso-auth-service/controllers/authController.js index 04c2ada..657a439 100644 --- a/sso-auth-service/controllers/authController.js +++ b/sso-auth-service/controllers/authController.js @@ -21,7 +21,9 @@ function createTokenPayload(user) { id: user.user_id, username: user.username, name: user.name, - worker_id: user.worker_id || null, + department_id: user.department_id || null, + department_name: user.department_name || user.department || null, + is_production: user.is_production || false, department: user.department, role: user.role, access_level: user.role, @@ -78,8 +80,10 @@ async function login(req, res, next) { username: user.username, name: user.name, department: user.department, + department_id: user.department_id || null, + department_name: user.department_name || user.department || null, + is_production: user.is_production || false, role: user.role, - worker_id: user.worker_id || null, system_access: payload.system_access } }); @@ -154,8 +158,10 @@ async function validate(req, res, next) { username: user.username, name: user.name, department: user.department, + department_id: user.department_id || null, + department_name: user.department_name || user.department || null, + is_production: user.is_production || false, role: user.role, - worker_id: user.worker_id || null, system_access: { system1: user.system1_access, system2: user.system2_access, diff --git a/sso-auth-service/models/userModel.js b/sso-auth-service/models/userModel.js index 6fbd8a3..5c32bca 100644 --- a/sso-auth-service/models/userModel.js +++ b/sso-auth-service/models/userModel.js @@ -84,7 +84,10 @@ async function hashPassword(password) { async function findByUsername(username) { const db = getPool(); const [rows] = await db.query( - 'SELECT * FROM sso_users WHERE username = ? AND is_active = TRUE', + `SELECT s.*, d.department_id, d.department_name, d.is_production + FROM sso_users s + LEFT JOIN departments d ON s.department_id = d.department_id + WHERE s.username = ? AND s.is_active = TRUE`, [username] ); return rows[0] || null; @@ -93,7 +96,10 @@ async function findByUsername(username) { async function findById(userId) { const db = getPool(); const [rows] = await db.query( - 'SELECT * FROM sso_users WHERE user_id = ?', + `SELECT s.*, d.department_id, d.department_name, d.is_production + FROM sso_users s + LEFT JOIN departments d ON s.department_id = d.department_id + WHERE s.user_id = ?`, [userId] ); return rows[0] || null; diff --git a/system1-factory/api/config/swagger.js b/system1-factory/api/config/swagger.js index ce2621a..e448dcb 100644 --- a/system1-factory/api/config/swagger.js +++ b/system1-factory/api/config/swagger.js @@ -154,10 +154,10 @@ const swaggerDefinition = { example: 'admin', description: '접근 권한 레벨' }, - worker_id: { + user_id: { type: 'integer', example: 1, - description: '연결된 작업자 ID' + description: '연결된 사용자 ID (sso_users)' }, is_active: { type: 'boolean', @@ -186,10 +186,10 @@ const swaggerDefinition = { Worker: { type: 'object', properties: { - worker_id: { + user_id: { type: 'integer', example: 1, - description: '작업자 ID' + description: '사용자 ID (sso_users)' }, worker_name: { type: 'string', @@ -347,10 +347,10 @@ const swaggerDefinition = { example: '2024-01-01', description: '작업 날짜' }, - worker_id: { + user_id: { type: 'integer', example: 1, - description: '작업자 ID' + description: '사용자 ID (sso_users)' }, project_id: { type: 'integer', diff --git a/system1-factory/api/controllers/attendanceController.js b/system1-factory/api/controllers/attendanceController.js index 124a4cd..f5ded7b 100644 --- a/system1-factory/api/controllers/attendanceController.js +++ b/system1-factory/api/controllers/attendanceController.js @@ -28,8 +28,8 @@ const getDailyAttendanceStatus = asyncHandler(async (req, res) => { * 일일 근태 기록 조회 */ const getDailyAttendanceRecords = asyncHandler(async (req, res) => { - const { date, worker_id } = req.query; - const data = await attendanceService.getDailyAttendanceRecordsService(date, worker_id); + const { date, user_id } = req.query; + const data = await attendanceService.getDailyAttendanceRecordsService(date, user_id); res.json({ success: true, @@ -42,8 +42,8 @@ const getDailyAttendanceRecords = asyncHandler(async (req, res) => { * 기간별 근태 기록 조회 (월별 조회용) */ const getAttendanceRecordsByRange = asyncHandler(async (req, res) => { - const { start_date, end_date, worker_id } = req.query; - const data = await attendanceService.getAttendanceRecordsByRangeService(start_date, end_date, worker_id); + const { start_date, end_date, user_id } = req.query; + const data = await attendanceService.getAttendanceRecordsByRangeService(start_date, end_date, user_id); res.json({ success: true, @@ -76,7 +76,7 @@ const upsertAttendanceRecord = asyncHandler(async (req, res) => { const processVacation = asyncHandler(async (req, res) => { const vacationData = { record_date: req.body.date, - worker_id: req.body.worker_id, + user_id: req.body.user_id, vacation_type_id: req.body.vacation_type, created_by: req.user?.user_id || req.user?.id }; @@ -96,7 +96,7 @@ const processVacation = asyncHandler(async (req, res) => { const approveOvertime = asyncHandler(async (req, res) => { const overtimeData = { record_date: req.body.date, - worker_id: req.body.worker_id, + user_id: req.body.user_id, overtime_approved: true, approved_by: req.user?.user_id || req.user?.id }; @@ -140,8 +140,8 @@ const getVacationTypes = asyncHandler(async (req, res) => { * 작업자 휴가 잔여 조회 */ const getWorkerVacationBalance = asyncHandler(async (req, res) => { - const { worker_id } = req.params; - const data = await attendanceService.getWorkerVacationBalanceService(parseInt(worker_id)); + const { user_id } = req.params; + const data = await attendanceService.getWorkerVacationBalanceService(parseInt(user_id)); res.json({ success: true, @@ -154,11 +154,11 @@ const getWorkerVacationBalance = asyncHandler(async (req, res) => { * 월별 근태 통계 */ const getMonthlyAttendanceStats = asyncHandler(async (req, res) => { - const { year, month, worker_id } = req.query; + const { year, month, user_id } = req.query; const data = await attendanceService.getMonthlyAttendanceStatsService( parseInt(year), parseInt(month), - worker_id ? parseInt(worker_id) : null + user_id ? parseInt(user_id) : null ); res.json({ @@ -186,7 +186,7 @@ const getCheckinList = asyncHandler(async (req, res) => { * 출근 체크 저장 (일괄 처리) */ const saveCheckins = asyncHandler(async (req, res) => { - const { date, checkins } = req.body; // checkins: [{worker_id, is_present}, ...] + const { date, checkins } = req.body; // checkins: [{user_id, is_present}, ...] const result = await attendanceService.saveCheckinsService(date, checkins); res.json({ diff --git a/system1-factory/api/controllers/dailyIssueReportController.js b/system1-factory/api/controllers/dailyIssueReportController.js index 6dca260..32a5ea9 100644 --- a/system1-factory/api/controllers/dailyIssueReportController.js +++ b/system1-factory/api/controllers/dailyIssueReportController.js @@ -14,10 +14,10 @@ const { asyncHandler } = require('../middlewares/errorHandler'); * 일일 이슈 보고서 생성 */ const createDailyIssueReport = asyncHandler(async (req, res) => { - // 프론트엔드에서 worker_ids 또는 worker_id로 보낼 수 있음 + // 프론트엔드에서 user_ids 또는 user_id로 보낼 수 있음 const issueData = { ...req.body, - worker_ids: req.body.worker_ids || req.body.worker_id + user_ids: req.body.user_ids || req.body.user_id }; const result = await dailyIssueReportService.createDailyIssueReportService(issueData); diff --git a/system1-factory/api/controllers/dailyWorkReportController.js b/system1-factory/api/controllers/dailyWorkReportController.js index e01621a..69f1a3c 100644 --- a/system1-factory/api/controllers/dailyWorkReportController.js +++ b/system1-factory/api/controllers/dailyWorkReportController.js @@ -36,19 +36,19 @@ const createDailyWorkReport = asyncHandler(async (req, res) => { * 기여자별 요약 조회 */ const getContributorsSummary = asyncHandler(async (req, res) => { - const { date, worker_id } = req.query; + const { date, user_id } = req.query; - if (!date || !worker_id) { - return res.status(400).json({ error: 'date와 worker_id가 필요합니다.' }); + if (!date || !user_id) { + return res.status(400).json({ error: 'date와 user_id가 필요합니다.' }); } - const data = await dailyWorkReportModel.getContributorsByDate(date, worker_id); + const data = await dailyWorkReportModel.getContributorsByDate(date, user_id); const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0); const result = { date, - worker_id, + user_id, contributors: data, total_contributors: data.length, grand_total_hours: totalHours @@ -61,13 +61,13 @@ const getContributorsSummary = asyncHandler(async (req, res) => { * 개인 누적 현황 조회 */ const getMyAccumulatedData = async (req, res) => { - const { date, worker_id } = req.query; + const { date, user_id } = req.query; const created_by = req.user?.user_id || req.user?.id; - if (!date || !worker_id) { + if (!date || !user_id) { return res.status(400).json({ - error: 'date와 worker_id가 필요합니다.', - example: 'date=2024-06-16&worker_id=1' + error: 'date와 user_id가 필요합니다.', + example: 'date=2024-06-16&user_id=1' }); } @@ -78,11 +78,11 @@ const getMyAccumulatedData = async (req, res) => { } try { - const data = await dailyWorkReportModel.getMyAccumulatedHours(date, worker_id, created_by); + const data = await dailyWorkReportModel.getMyAccumulatedHours(date, user_id, created_by); res.json({ date, - worker_id, + user_id, created_by, my_data: data, timestamp: new Date().toISOString() @@ -187,14 +187,14 @@ const getDailyWorkReportsByDate = async (req, res) => { * 작업보고서 검색 (페이지네이션 포함) */ const searchWorkReports = async (req, res) => { - const { start_date, end_date, worker_id, project_id, work_status_id, page = 1, limit = 20 } = req.query; + const { start_date, end_date, user_id, project_id, work_status_id, page = 1, limit = 20 } = req.query; const created_by = req.user?.user_id || req.user?.id; if (!start_date || !end_date) { return res.status(400).json({ error: 'start_date와 end_date가 필요합니다.', example: 'start_date=2024-01-01&end_date=2024-01-31', - optional: ['worker_id', 'project_id', 'work_status_id', 'page', 'limit'] + optional: ['user_id', 'project_id', 'work_status_id', 'page', 'limit'] }); } @@ -207,7 +207,7 @@ const searchWorkReports = async (req, res) => { const searchParams = { start_date, end_date, - worker_id: worker_id ? parseInt(worker_id) : null, + user_id: user_id ? parseInt(user_id) : null, project_id: project_id ? parseInt(project_id) : null, work_status_id: work_status_id ? parseInt(work_status_id) : null, created_by, @@ -377,7 +377,7 @@ const removeDailyWorkReport = async (req, res) => { * 작업자의 특정 날짜 전체 삭제 */ const removeDailyWorkReportByDateAndWorker = async (req, res) => { - const { date, worker_id } = req.params; + const { date, user_id } = req.params; const deleted_by = req.user?.user_id || req.user?.id; const access_level = req.user?.access_level || req.user?.role; @@ -397,20 +397,20 @@ const removeDailyWorkReportByDateAndWorker = async (req, res) => { } try { - const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by); + const affectedRows = await dailyWorkReportModel.removeByDateAndWorker(date, user_id, deleted_by); if (affectedRows === 0) { return res.status(404).json({ error: '삭제할 작업보고서를 찾을 수 없습니다.', date: date, - worker_id: worker_id + user_id: user_id }); } res.json({ - message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`, + message: `${date} 날짜의 작업자 ${user_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`, date, - worker_id, + user_id, affected_rows: affectedRows, deleted_by, timestamp: new Date().toISOString() @@ -642,21 +642,21 @@ const deleteErrorType = asyncHandler(async (req, res) => { * 누적 현황 조회 */ const getAccumulatedReports = async (req, res) => { - const { date, worker_id } = req.query; + const { date, user_id } = req.query; - if (!date || !worker_id) { + if (!date || !user_id) { return res.status(400).json({ - error: 'date와 worker_id가 필요합니다.', - example: 'date=2024-06-16&worker_id=1' + error: 'date와 user_id가 필요합니다.', + example: 'date=2024-06-16&user_id=1' }); } try { - const data = await dailyWorkReportModel.getAccumulatedReportsByDate(date, worker_id); + const data = await dailyWorkReportModel.getAccumulatedReportsByDate(date, user_id); res.json({ date, - worker_id, + user_id, total_entries: data.length, accumulated_data: data, timestamp: new Date().toISOString() @@ -678,7 +678,7 @@ const createFromTbm = async (req, res) => { const { tbm_assignment_id, tbm_session_id, - worker_id, + user_id, project_id, work_type_id, report_date, @@ -691,10 +691,10 @@ const createFromTbm = async (req, res) => { } = req.body; // 필수 필드 검증 - if (!tbm_assignment_id || !tbm_session_id || !worker_id || !report_date || !total_hours) { + if (!tbm_assignment_id || !tbm_session_id || !user_id || !report_date || !total_hours) { return res.status(400).json({ success: false, - message: '필수 필드가 누락되었습니다. (assignment_id, session_id, worker_id, report_date, total_hours)' + message: '필수 필드가 누락되었습니다. (assignment_id, session_id, user_id, report_date, total_hours)' }); } @@ -704,7 +704,7 @@ const createFromTbm = async (req, res) => { const reportData = { tbm_assignment_id, tbm_session_id, - worker_id, + user_id, project_id, work_type_id, report_date, diff --git a/system1-factory/api/controllers/monthlyStatusController.js b/system1-factory/api/controllers/monthlyStatusController.js index c34c5df..cc57a44 100644 --- a/system1-factory/api/controllers/monthlyStatusController.js +++ b/system1-factory/api/controllers/monthlyStatusController.js @@ -101,7 +101,7 @@ const getDailyWorkerDetails = asyncHandler(async (req, res) => { // 데이터 변환 const formattedData = workerDetails.map(worker => ({ - workerId: worker.worker_id, + userId: worker.user_id, workerName: worker.worker_name, jobType: worker.job_type, totalHours: parseFloat(worker.total_work_hours || 0), diff --git a/system1-factory/api/controllers/patrolController.js b/system1-factory/api/controllers/patrolController.js index e810a9c..c46b990 100644 --- a/system1-factory/api/controllers/patrolController.js +++ b/system1-factory/api/controllers/patrolController.js @@ -416,7 +416,7 @@ const PatrolController = { LEFT JOIN tasks t ON ts.task_id = t.task_id LEFT JOIN work_types wt ON t.work_type_id = wt.id LEFT JOIN users u ON ts.leader_id = u.user_id - LEFT JOIN workers w ON ts.leader_worker_id = w.worker_id + LEFT JOIN workers w ON ts.leader_user_id = w.user_id WHERE ts.category_id = ? AND ts.session_date = ? ORDER BY ts.created_at DESC `, [categoryId, targetDate]); @@ -433,7 +433,7 @@ const PatrolController = { SELECT tta.assignment_id, w.worker_name, w.occupation, tta.attendance_status, tta.signature_image FROM tbm_team_assignments tta - JOIN workers w ON tta.worker_id = w.worker_id + JOIN workers w ON tta.user_id = w.user_id WHERE tta.session_id = ? ORDER BY w.worker_name `, [session.session_id]); diff --git a/system1-factory/api/controllers/tbmController.js b/system1-factory/api/controllers/tbmController.js index 52447ea..cb44eff 100644 --- a/system1-factory/api/controllers/tbmController.js +++ b/system1-factory/api/controllers/tbmController.js @@ -10,7 +10,7 @@ const TbmController = { try { const sessionData = { session_date: req.body.session_date, - leader_id: req.body.leader_id || null, + leader_user_id: req.body.leader_user_id || null, project_id: req.body.project_id || null, work_location: req.body.work_location || null, work_description: req.body.work_description || null, @@ -135,7 +135,7 @@ const TbmController = { try { const assignmentData = { session_id: req.params.sessionId, - worker_id: req.body.worker_id, + user_id: req.body.user_id, assigned_role: req.body.assigned_role || null, work_detail: req.body.work_detail || null, is_present: req.body.is_present, @@ -148,7 +148,7 @@ const TbmController = { work_hours: req.body.work_hours !== undefined ? req.body.work_hours : undefined }; - if (!assignmentData.worker_id) { + if (!assignmentData.user_id) { return res.status(400).json({ success: false, message: '작업자 ID가 필요합니다.' }); } @@ -164,7 +164,7 @@ const TbmController = { try { const assignmentData = { session_id: req.params.sessionId, - worker_id: req.body.worker_id, + user_id: req.body.user_id, work_hours: req.body.work_hours, project_id: req.body.project_id || null, work_type_id: req.body.work_type_id || null, @@ -173,7 +173,7 @@ const TbmController = { workplace_id: req.body.workplace_id || null }; - if (!assignmentData.worker_id || !assignmentData.work_hours) { + if (!assignmentData.user_id || !assignmentData.work_hours) { return res.status(400).json({ success: false, message: '작업자 ID와 작업시간이 필요합니다.' }); } @@ -218,8 +218,8 @@ const TbmController = { removeTeamMember: async (req, res) => { try { - const { sessionId, workerId } = req.params; - const result = await TbmModel.removeTeamMember(sessionId, workerId); + const { sessionId, userId } = req.params; + const result = await TbmModel.removeTeamMember(sessionId, userId); if (result.affectedRows === 0) { return res.status(404).json({ success: false, message: '팀원을 찾을 수 없습니다.' }); @@ -432,17 +432,17 @@ const TbmController = { try { const handoverData = { session_id: req.body.session_id, - from_leader_id: req.body.from_leader_id, - to_leader_id: req.body.to_leader_id, + from_leader_user_id: req.body.from_leader_user_id, + to_leader_user_id: req.body.to_leader_user_id, handover_date: req.body.handover_date, handover_time: req.body.handover_time || null, reason: req.body.reason, handover_notes: req.body.handover_notes || null, - worker_ids: req.body.worker_ids || [] + user_ids: req.body.user_ids || [] }; - if (!handoverData.session_id || !handoverData.from_leader_id || - !handoverData.to_leader_id || !handoverData.handover_date || !handoverData.reason) { + if (!handoverData.session_id || !handoverData.from_leader_user_id || + !handoverData.to_leader_user_id || !handoverData.handover_date || !handoverData.reason) { return res.status(400).json({ success: false, message: '필수 정보가 누락되었습니다.' }); } @@ -483,7 +483,7 @@ const TbmController = { getMyPendingHandovers: async (req, res) => { try { - const toLeaderId = req.user.worker_id; + const toLeaderId = req.user.user_id; if (!toLeaderId) { return res.status(400).json({ success: false, message: '작업자 정보를 찾을 수 없습니다.' }); } @@ -532,10 +532,10 @@ const TbmController = { createTransfer: async (req, res) => { try { - const { transfer_type, worker_id, source_session_id, dest_session_id, hours, + const { transfer_type, user_id, source_session_id, dest_session_id, hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id } = req.body; - if (!transfer_type || !worker_id || !source_session_id || !dest_session_id || !hours) { + if (!transfer_type || !user_id || !source_session_id || !dest_session_id || !hours) { return res.status(400).json({ success: false, message: '필수 정보가 누락되었습니다.' }); } @@ -545,7 +545,7 @@ const TbmController = { String(today.getDate()).padStart(2, '0'); const transferData = { - transfer_type, worker_id, source_session_id, dest_session_id, + transfer_type, user_id, source_session_id, dest_session_id, hours, initiated_by: req.user.user_id, transfer_date: transferDate, project_id, work_type_id, task_id, workplace_category_id, workplace_id }; diff --git a/system1-factory/api/controllers/vacationBalanceController.js b/system1-factory/api/controllers/vacationBalanceController.js index 2231dc7..420e2d7 100644 --- a/system1-factory/api/controllers/vacationBalanceController.js +++ b/system1-factory/api/controllers/vacationBalanceController.js @@ -10,12 +10,12 @@ const logger = require('../utils/logger'); const vacationBalanceController = { /** * 특정 작업자의 휴가 잔액 조회 (특정 연도) - * GET /api/vacation-balances/worker/:workerId/year/:year + * GET /api/vacation-balances/user/:userId/year/:year */ async getByWorkerAndYear(req, res) { try { - const { workerId, year } = req.params; - const results = await vacationBalanceModel.getByWorkerAndYear(workerId, year); + const { userId, year } = req.params; + const results = await vacationBalanceModel.getByWorkerAndYear(userId, year); res.json({ success: true, data: results }); } catch (error) { logger.error('휴가 잔액 조회 오류:', error); @@ -44,18 +44,18 @@ const vacationBalanceController = { */ async createBalance(req, res) { try { - const { worker_id, vacation_type_id, year, total_days, used_days, notes } = req.body; + const { user_id, vacation_type_id, year, total_days, used_days, notes } = req.body; const created_by = req.user.user_id; - if (!worker_id || !vacation_type_id || !year || total_days === undefined) { + if (!user_id || !vacation_type_id || !year || total_days === undefined) { return res.status(400).json({ success: false, - message: '필수 필드가 누락되었습니다 (worker_id, vacation_type_id, year, total_days)' + message: '필수 필드가 누락되었습니다 (user_id, vacation_type_id, year, total_days)' }); } // 중복 체크 - const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, vacation_type_id, year); + const existing = await vacationBalanceModel.getByWorkerTypeYear(user_id, vacation_type_id, year); if (existing && existing.length > 0) { return res.status(400).json({ success: false, @@ -64,7 +64,7 @@ const vacationBalanceController = { } const balanceData = { - worker_id, + user_id, vacation_type_id, year, total_days, @@ -142,13 +142,13 @@ const vacationBalanceController = { */ async autoCalculateAndCreate(req, res) { try { - const { worker_id, hire_date, year } = req.body; + const { user_id, hire_date, year } = req.body; const created_by = req.user.user_id; - if (!worker_id || !hire_date || !year) { + if (!user_id || !hire_date || !year) { return res.status(400).json({ success: false, - message: '필수 필드가 누락되었습니다 (worker_id, hire_date, year)' + message: '필수 필드가 누락되었습니다 (user_id, hire_date, year)' }); } @@ -163,7 +163,7 @@ const vacationBalanceController = { const annualTypeId = types[0].id; // 중복 체크 - const existing = await vacationBalanceModel.getByWorkerTypeYear(worker_id, annualTypeId, year); + const existing = await vacationBalanceModel.getByWorkerTypeYear(user_id, annualTypeId, year); if (existing && existing.length > 0) { return res.status(400).json({ success: false, @@ -172,7 +172,7 @@ const vacationBalanceController = { } const balanceData = { - worker_id, + user_id, vacation_type_id: annualTypeId, year, total_days: annualDays, @@ -213,9 +213,9 @@ const vacationBalanceController = { let errorCount = 0; for (const balance of balances) { - const { worker_id, vacation_type_id, year, total_days, notes } = balance; + const { user_id, vacation_type_id, year, total_days, notes } = balance; - if (!worker_id || !vacation_type_id || !year || total_days === undefined) { + if (!user_id || !vacation_type_id || !year || total_days === undefined) { errorCount++; continue; } @@ -223,7 +223,7 @@ const vacationBalanceController = { try { const query = ` INSERT INTO vacation_balance_details - (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) + (user_id, vacation_type_id, year, total_days, used_days, notes, created_by) VALUES (?, ?, ?, ?, 0, ?, ?) ON DUPLICATE KEY UPDATE total_days = VALUES(total_days), @@ -231,7 +231,7 @@ const vacationBalanceController = { updated_at = NOW() `; - await db.query(query, [worker_id, vacation_type_id, year, total_days, notes || null, created_by]); + await db.query(query, [user_id, vacation_type_id, year, total_days, notes || null, created_by]); successCount++; } catch (err) { logger.error('휴가 잔액 저장 오류:', err); @@ -252,12 +252,12 @@ const vacationBalanceController = { /** * 작업자의 사용 가능한 휴가 일수 조회 - * GET /api/vacation-balances/worker/:workerId/year/:year/available + * GET /api/vacation-balances/user/:userId/year/:year/available */ async getAvailableDays(req, res) { try { - const { workerId, year } = req.params; - const results = await vacationBalanceModel.getAvailableVacationDays(workerId, year); + const { userId, year } = req.params; + const results = await vacationBalanceModel.getAvailableVacationDays(userId, year); res.json({ success: true, data: results }); } catch (error) { logger.error('사용 가능 휴가 조회 오류:', error); diff --git a/system1-factory/api/controllers/vacationRequestController.js b/system1-factory/api/controllers/vacationRequestController.js index 318af1f..8ec44a7 100644 --- a/system1-factory/api/controllers/vacationRequestController.js +++ b/system1-factory/api/controllers/vacationRequestController.js @@ -12,10 +12,10 @@ const vacationRequestController = { */ async createRequest(req, res) { try { - const { worker_id, vacation_type_id, start_date, end_date, days_used, reason } = req.body; + const { user_id, vacation_type_id, start_date, end_date, days_used, reason } = req.body; const requested_by = req.user.user_id; - if (!worker_id || !vacation_type_id || !start_date || !end_date || !days_used) { + if (!user_id || !vacation_type_id || !start_date || !end_date || !days_used) { return res.status(400).json({ success: false, message: '필수 필드가 누락되었습니다' }); } @@ -24,13 +24,13 @@ const vacationRequestController = { } // 기간 중복 체크 - const overlapRows = await vacationRequestModel.checkOverlap(worker_id, start_date, end_date); + const overlapRows = await vacationRequestModel.checkOverlap(user_id, start_date, end_date); if (overlapRows[0].count > 0) { return res.status(400).json({ success: false, message: '해당 기간에 이미 신청된 휴가가 있습니다' }); } const result = await vacationRequestModel.create({ - worker_id, vacation_type_id, start_date, end_date, + user_id, vacation_type_id, start_date, end_date, days_used, reason: reason || null, status: 'pending', requested_by }); @@ -51,7 +51,7 @@ const vacationRequestController = { async getAllRequests(req, res) { try { const filters = { - worker_id: req.query.worker_id, + user_id: req.query.user_id, status: req.query.status, start_date: req.query.start_date, end_date: req.query.end_date, @@ -60,8 +60,8 @@ const vacationRequestController = { // 일반 사용자는 자신의 신청만 조회 가능 if (req.user.access_level !== 'system') { - if (req.user.worker_id) { - filters.worker_id = req.user.worker_id; + if (req.user.user_id) { + filters.user_id = req.user.user_id; } else { return res.status(403).json({ success: false, message: '권한이 없습니다' }); } @@ -88,7 +88,7 @@ const vacationRequestController = { const request = results[0]; - if (req.user.access_level !== 'system' && req.user.worker_id !== request.worker_id) { + if (req.user.access_level !== 'system' && req.user.user_id !== request.user_id) { return res.status(403).json({ success: false, message: '권한이 없습니다' }); } @@ -114,7 +114,7 @@ const vacationRequestController = { const existingRequest = results[0]; - if (req.user.access_level !== 'system' && req.user.worker_id !== existingRequest.worker_id) { + if (req.user.access_level !== 'system' && req.user.user_id !== existingRequest.user_id) { return res.status(403).json({ success: false, message: '권한이 없습니다' }); } @@ -134,7 +134,7 @@ const vacationRequestController = { const newEndDate = end_date || existingRequest.end_date; const overlapRows = await vacationRequestModel.checkOverlap( - existingRequest.worker_id, newStartDate, newEndDate, id + existingRequest.user_id, newStartDate, newEndDate, id ); if (overlapRows[0].count > 0) { return res.status(400).json({ success: false, message: '해당 기간에 이미 신청된 휴가가 있습니다' }); @@ -163,7 +163,7 @@ const vacationRequestController = { const existingRequest = results[0]; - if (req.user.access_level !== 'system' && req.user.worker_id !== existingRequest.worker_id) { + if (req.user.access_level !== 'system' && req.user.user_id !== existingRequest.user_id) { return res.status(403).json({ success: false, message: '권한이 없습니다' }); } diff --git a/system1-factory/api/controllers/workAnalysisController.js b/system1-factory/api/controllers/workAnalysisController.js index 506e003..881a231 100644 --- a/system1-factory/api/controllers/workAnalysisController.js +++ b/system1-factory/api/controllers/workAnalysisController.js @@ -353,10 +353,10 @@ const getWorkerSpecialization = asyncHandler(async (req, res) => { // 작업자별로 그룹화하여 정리 const groupedData = specializationData.reduce((acc, item) => { - if (!acc[item.worker_id]) { - acc[item.worker_id] = []; + if (!acc[item.user_id]) { + acc[item.user_id] = []; } - acc[item.worker_id].push({ + acc[item.user_id].push({ work_type_id: item.work_type_id, project_id: item.project_id, totalHours: item.totalHours, diff --git a/system1-factory/api/controllers/workReportAnalysisController.js b/system1-factory/api/controllers/workReportAnalysisController.js index 2e376ac..32020c1 100644 --- a/system1-factory/api/controllers/workReportAnalysisController.js +++ b/system1-factory/api/controllers/workReportAnalysisController.js @@ -31,9 +31,9 @@ const getAnalysisFilters = asyncHandler(async (req, res) => { // 작업자 목록 const [workers] = await db.query(` - SELECT DISTINCT w.worker_id, w.worker_name + SELECT DISTINCT w.user_id, w.worker_name FROM workers w - INNER JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id + INNER JOIN daily_work_reports dwr ON w.user_id = dwr.user_id ORDER BY w.worker_name `); @@ -79,7 +79,7 @@ const getAnalysisFilters = asyncHandler(async (req, res) => { * 기간별 작업 분석 데이터 조회 */ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { - const { start_date, end_date, project_id, worker_id } = req.query; + const { start_date, end_date, project_id, user_id } = req.query; if (!start_date || !end_date) { throw new ValidationError('start_date와 end_date가 필요합니다', { @@ -93,7 +93,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { start_date, end_date, project_id, - worker_id + user_id }); const db = await getDb(); @@ -108,9 +108,9 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { queryParams.push(project_id); } - if (worker_id) { - whereConditions.push('dwr.worker_id = ?'); - queryParams.push(worker_id); + if (user_id) { + whereConditions.push('dwr.user_id = ?'); + queryParams.push(user_id); } const whereClause = whereConditions.join(' AND '); @@ -120,7 +120,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { SELECT COUNT(*) as total_entries, SUM(dwr.work_hours) as total_hours, - COUNT(DISTINCT dwr.worker_id) as unique_workers, + COUNT(DISTINCT dwr.user_id) as unique_workers, COUNT(DISTINCT dwr.project_id) as unique_projects, COUNT(DISTINCT dwr.report_date) as working_days, AVG(dwr.work_hours) as avg_hours_per_entry, @@ -139,7 +139,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { dwr.report_date, SUM(dwr.work_hours) as daily_hours, COUNT(*) as daily_entries, - COUNT(DISTINCT dwr.worker_id) as daily_workers + COUNT(DISTINCT dwr.user_id) as daily_workers FROM daily_work_reports dwr WHERE ${whereClause} GROUP BY dwr.report_date @@ -202,7 +202,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { // 6. 작업자별 성과 분석 const workerAnalysisSql = ` SELECT - w.worker_id, + w.user_id, w.worker_name, COUNT(*) as total_entries, SUM(dwr.work_hours) as total_hours, @@ -212,9 +212,9 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id WHERE ${whereClause} - GROUP BY w.worker_id, w.worker_name + GROUP BY w.user_id, w.worker_name ORDER BY total_hours DESC `; @@ -227,7 +227,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { p.project_name, COUNT(*) as total_entries, SUM(dwr.work_hours) as total_hours, - COUNT(DISTINCT dwr.worker_id) as workers_count, + COUNT(DISTINCT dwr.user_id) as workers_count, COUNT(DISTINCT dwr.report_date) as working_days, AVG(dwr.work_hours) as avg_hours_per_entry, COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, @@ -259,7 +259,7 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { workerAnalysis, projectAnalysis, period: { start_date, end_date }, - filters: { project_id, worker_id } + filters: { project_id, user_id } }, message: '기간별 분석 데이터 조회 성공' }); @@ -311,7 +311,7 @@ const getProjectAnalysis = asyncHandler(async (req, res) => { p.project_name, SUM(dwr.work_hours) as total_hours, COUNT(*) as total_entries, - COUNT(DISTINCT dwr.worker_id) as workers_count, + COUNT(DISTINCT dwr.user_id) as workers_count, COUNT(DISTINCT dwr.report_date) as working_days, AVG(dwr.work_hours) as avg_hours_per_entry FROM daily_work_reports dwr @@ -351,7 +351,7 @@ const getProjectAnalysis = asyncHandler(async (req, res) => { * 작업자별 상세 분석 */ const getWorkerAnalysis = asyncHandler(async (req, res) => { - const { start_date, end_date, worker_id } = req.query; + const { start_date, end_date, user_id } = req.query; if (!start_date || !end_date) { throw new ValidationError('start_date와 end_date가 필요합니다', { @@ -363,7 +363,7 @@ const getWorkerAnalysis = asyncHandler(async (req, res) => { logger.info('작업자별 분석 조회 요청', { start_date, end_date, - worker_id + user_id }); const db = await getDb(); @@ -372,16 +372,16 @@ const getWorkerAnalysis = asyncHandler(async (req, res) => { let whereConditions = ['dwr.report_date BETWEEN ? AND ?']; let queryParams = [start_date, end_date]; - if (worker_id) { - whereConditions.push('dwr.worker_id = ?'); - queryParams.push(worker_id); + if (user_id) { + whereConditions.push('dwr.user_id = ?'); + queryParams.push(user_id); } const whereClause = whereConditions.join(' AND '); const workerStatsSql = ` SELECT - dwr.worker_id, + dwr.user_id, w.worker_name, SUM(dwr.work_hours) as total_hours, COUNT(*) as total_entries, @@ -389,9 +389,9 @@ const getWorkerAnalysis = asyncHandler(async (req, res) => { COUNT(DISTINCT dwr.report_date) as working_days, AVG(dwr.work_hours) as avg_hours_per_entry FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id WHERE ${whereClause} - GROUP BY dwr.worker_id + GROUP BY dwr.user_id ORDER BY total_hours DESC `; diff --git a/system1-factory/api/controllers/workerController.js b/system1-factory/api/controllers/workerController.js index e71332b..18c745f 100644 --- a/system1-factory/api/controllers/workerController.js +++ b/system1-factory/api/controllers/workerController.js @@ -103,7 +103,7 @@ exports.getAllWorkers = asyncHandler(async (req, res) => { * 단일 작업자 조회 */ exports.getWorkerById = asyncHandler(async (req, res) => { - const id = parseInt(req.params.worker_id, 10); + const id = parseInt(req.params.user_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); @@ -126,7 +126,7 @@ exports.getWorkerById = asyncHandler(async (req, res) => { * 작업자 수정 */ exports.updateWorker = asyncHandler(async (req, res) => { - const id = parseInt(req.params.worker_id, 10); + const id = parseInt(req.params.user_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); @@ -252,7 +252,7 @@ exports.updateWorker = asyncHandler(async (req, res) => { * 작업자 삭제 */ exports.removeWorker = asyncHandler(async (req, res) => { - const id = parseInt(req.params.worker_id, 10); + const id = parseInt(req.params.user_id, 10); if (isNaN(id)) { throw new ValidationError('유효하지 않은 작업자 ID입니다'); diff --git a/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js b/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js new file mode 100644 index 0000000..f5c675a --- /dev/null +++ b/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js @@ -0,0 +1,233 @@ +/** + * worker_id → user_id 통합 마이그레이션 (Phase 1) + * + * 비파괴적 추가: 기존 worker_id 컬럼은 유지하면서 user_id 컬럼을 추가하고 백필. + * 코드 변경 전이므로 기존 시스템 동작에 영향 없음. + * + * 대상 테이블: + * 1. departments - is_production 플래그 추가 + * 2. sso_users - department_id 추가 (없는 경우) + * 3. workers - user_id 추가 + 매핑 백필 + * 4~15. 12개 참조 테이블 - user_id 추가 + 백필 + * + * @since 2026-03-05 + */ + +exports.up = async function(knex) { + // ============================================================ + // 1. departments 테이블에 is_production 플래그 추가 + // ============================================================ + const hasIsProduction = await knex.schema.hasColumn('departments', 'is_production'); + if (!hasIsProduction) { + await knex.schema.table('departments', (table) => { + table.boolean('is_production').defaultTo(false).comment('생산직 부서 여부'); + }); + await knex.raw(`UPDATE departments SET is_production = TRUE WHERE department_name LIKE '%생산%'`); + console.log('✅ departments.is_production 추가 완료'); + } + + // ============================================================ + // 2. sso_users에 department_id 추가 (없는 경우) + // ============================================================ + const hasSsoDeptId = await knex.schema.hasColumn('sso_users', 'department_id'); + if (!hasSsoDeptId) { + await knex.schema.table('sso_users', (table) => { + table.integer('department_id').unsigned().defaultTo(null).comment('소속 부서 ID'); + }); + // 기존 department(문자열) → department_id(FK) 매핑 + await knex.raw(` + UPDATE sso_users s + INNER JOIN departments d ON s.department = d.department_name + SET s.department_id = d.department_id + WHERE s.department IS NOT NULL + `); + console.log('✅ sso_users.department_id 추가 및 백필 완료'); + } + + // ============================================================ + // 3. workers 테이블에 user_id 추가 + 매핑 백필 + // ============================================================ + const hasWorkersUserId = await knex.schema.hasColumn('workers', 'user_id'); + if (!hasWorkersUserId) { + await knex.schema.table('workers', (table) => { + table.integer('user_id').unsigned().defaultTo(null).after('worker_id') + .comment('sso_users.user_id 매핑'); + }); + // users 테이블을 경유하여 sso_users.user_id 매핑 + await knex.raw(` + UPDATE workers w + INNER JOIN users u ON u.worker_id = w.worker_id + INNER JOIN sso_users s ON s.username = u.username + SET w.user_id = s.user_id + `); + // user_id에 인덱스 추가 + await knex.raw(`ALTER TABLE workers ADD INDEX idx_workers_user_id (user_id)`); + console.log('✅ workers.user_id 추가 및 백필 완료'); + } + + // ============================================================ + // 4~15. 12개 참조 테이블에 user_id 컬럼 추가 + 백필 + // ============================================================ + + // worker_id 컬럼을 가진 테이블들 + const tablesWithWorkerId = [ + 'tbm_team_assignments', + 'tbm_transfers', + 'daily_work_reports', + 'daily_attendance_records', + 'worker_vacation_balance', + 'vacation_requests', + 'vacation_balance_details', + 'worker_groups', + 'monthly_worker_status', + ]; + + for (const tableName of tablesWithWorkerId) { + const tableExists = await knex.schema.hasTable(tableName); + if (!tableExists) { + console.log(`⏭️ ${tableName} 테이블이 존재하지 않음, 건너뜀`); + continue; + } + + const hasUserId = await knex.schema.hasColumn(tableName, 'user_id'); + if (!hasUserId) { + await knex.schema.table(tableName, (table) => { + table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id'); + }); + // 백필: workers 테이블의 user_id 매핑 사용 + await knex.raw(` + UPDATE ${tableName} t + INNER JOIN workers w ON t.worker_id = w.worker_id + SET t.user_id = w.user_id + WHERE w.user_id IS NOT NULL + `); + // 인덱스 추가 + await knex.raw(`ALTER TABLE ${tableName} ADD INDEX idx_${tableName}_user_id (user_id)`); + console.log(`✅ ${tableName}.user_id 추가 및 백필 완료`); + } + } + + // DailyIssueReports (대소문자 다른 테이블명) + const hasDIR = await knex.schema.hasTable('DailyIssueReports'); + if (hasDIR) { + const hasDIRUserId = await knex.schema.hasColumn('DailyIssueReports', 'user_id'); + if (!hasDIRUserId) { + await knex.schema.table('DailyIssueReports', (table) => { + table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id'); + }); + await knex.raw(` + UPDATE DailyIssueReports t + INNER JOIN workers w ON t.worker_id = w.worker_id + SET t.user_id = w.user_id + WHERE w.user_id IS NOT NULL + `); + console.log('✅ DailyIssueReports.user_id 추가 및 백필 완료'); + } + } + + // WorkReports (대소문자 다른 테이블명) + const hasWR = await knex.schema.hasTable('WorkReports'); + if (hasWR) { + const hasWRUserId = await knex.schema.hasColumn('WorkReports', 'user_id'); + if (!hasWRUserId) { + await knex.schema.table('WorkReports', (table) => { + table.integer('user_id').unsigned().defaultTo(null).comment('sso_users.user_id'); + }); + await knex.raw(` + UPDATE WorkReports t + INNER JOIN workers w ON t.worker_id = w.worker_id + SET t.user_id = w.user_id + WHERE w.user_id IS NOT NULL + `); + console.log('✅ WorkReports.user_id 추가 및 백필 완료'); + } + } + + // tbm_sessions: leader_id → leader_user_id 추가 + const hasLeaderUserId = await knex.schema.hasColumn('tbm_sessions', 'leader_user_id'); + if (!hasLeaderUserId) { + await knex.schema.table('tbm_sessions', (table) => { + table.integer('leader_user_id').unsigned().defaultTo(null).comment('조장 sso_users.user_id'); + }); + await knex.raw(` + UPDATE tbm_sessions t + INNER JOIN workers w ON t.leader_id = w.worker_id + SET t.leader_user_id = w.user_id + WHERE w.user_id IS NOT NULL + `); + await knex.raw(`ALTER TABLE tbm_sessions ADD INDEX idx_tbm_sessions_leader_user_id (leader_user_id)`); + console.log('✅ tbm_sessions.leader_user_id 추가 및 백필 완료'); + } + + // team_handovers: from/to_leader_id → from/to_leader_user_id 추가 + const hasFromLeaderUserId = await knex.schema.hasColumn('team_handovers', 'from_leader_user_id'); + if (!hasFromLeaderUserId) { + await knex.schema.table('team_handovers', (table) => { + table.integer('from_leader_user_id').unsigned().defaultTo(null).comment('인계자 sso_users.user_id'); + table.integer('to_leader_user_id').unsigned().defaultTo(null).comment('인수자 sso_users.user_id'); + }); + await knex.raw(` + UPDATE team_handovers t + INNER JOIN workers w1 ON t.from_leader_id = w1.worker_id + SET t.from_leader_user_id = w1.user_id + WHERE w1.user_id IS NOT NULL + `); + await knex.raw(` + UPDATE team_handovers t + INNER JOIN workers w2 ON t.to_leader_id = w2.worker_id + SET t.to_leader_user_id = w2.user_id + WHERE w2.user_id IS NOT NULL + `); + console.log('✅ team_handovers.from/to_leader_user_id 추가 및 백필 완료'); + } + + console.log('🎉 Phase 1 마이그레이션 완료: 모든 테이블에 user_id 컬럼 추가 및 백필 완료'); +}; + +exports.down = async function(knex) { + // user_id 컬럼 제거 (롤백) + const columnsToRemove = [ + ['departments', 'is_production'], + ['workers', 'user_id'], + ['tbm_team_assignments', 'user_id'], + ['tbm_transfers', 'user_id'], + ['daily_work_reports', 'user_id'], + ['daily_attendance_records', 'user_id'], + ['worker_vacation_balance', 'user_id'], + ['vacation_requests', 'user_id'], + ['vacation_balance_details', 'user_id'], + ['worker_groups', 'user_id'], + ['monthly_worker_status', 'user_id'], + ['tbm_sessions', 'leader_user_id'], + ['team_handovers', 'from_leader_user_id'], + ['team_handovers', 'to_leader_user_id'], + ]; + + for (const [tableName, columnName] of columnsToRemove) { + const tableExists = await knex.schema.hasTable(tableName); + if (!tableExists) continue; + const hasColumn = await knex.schema.hasColumn(tableName, columnName); + if (hasColumn) { + await knex.schema.table(tableName, (table) => { + table.dropColumn(columnName); + }); + console.log(`↩️ ${tableName}.${columnName} 제거`); + } + } + + // 대소문자 다른 테이블 + for (const tableName of ['DailyIssueReports', 'WorkReports']) { + const tableExists = await knex.schema.hasTable(tableName); + if (tableExists) { + const hasColumn = await knex.schema.hasColumn(tableName, 'user_id'); + if (hasColumn) { + await knex.schema.table(tableName, (table) => { + table.dropColumn('user_id'); + }); + console.log(`↩️ ${tableName}.user_id 제거`); + } + } + } + + // sso_users.department_id는 유지 (다른 마이그레이션에서 관리) +}; diff --git a/system1-factory/api/db/migrations/20260205_fix_work_type_id_data.js b/system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js similarity index 100% rename from system1-factory/api/db/migrations/20260205_fix_work_type_id_data.js rename to system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js diff --git a/system1-factory/api/middlewares/auth.js b/system1-factory/api/middlewares/auth.js index 990fecf..a9d1257 100644 --- a/system1-factory/api/middlewares/auth.js +++ b/system1-factory/api/middlewares/auth.js @@ -229,7 +229,7 @@ const requireMinLevel = (minLevel) => { * requireAuth 미들웨어가 먼저 실행되어야 합니다. * * @param {Object} options - 옵션 객체 - * @param {string} options.resourceField - 리소스 ID를 가져올 req 필드 (예: 'params.user_id', 'body.worker_id') + * @param {string} options.resourceField - 리소스 ID를 가져올 req 필드 (예: 'params.user_id', 'body.user_id') * @param {string} options.userField - 사용자 ID 필드명 (기본값: 'user_id', 'id'도 자동 시도) * @param {string[]} options.adminRoles - 관리자로 인정할 역할들 (기본값: ['admin', 'system']) * @returns {Function} Express 미들웨어 함수 @@ -242,9 +242,9 @@ const requireMinLevel = (minLevel) => { * resourceField: 'params.user_id' * }), updateUser); * - * // 요청 body의 worker_id로 체크, support_team도 관리자로 인정 + * // 요청 body의 user_id로 체크, support_team도 관리자로 인정 * router.delete('/reports/:id', requireAuth, requireOwnerOrAdmin({ - * resourceField: 'body.worker_id', + * resourceField: 'body.user_id', * adminRoles: ['admin', 'system', 'support_team'] * }), deleteReport); */ diff --git a/system1-factory/api/models/WorkAnalysis.js b/system1-factory/api/models/WorkAnalysis.js index 8513ace..0fd7920 100644 --- a/system1-factory/api/models/WorkAnalysis.js +++ b/system1-factory/api/models/WorkAnalysis.js @@ -12,7 +12,7 @@ class WorkAnalysis { COALESCE(SUM(work_hours), 0) as total_hours, COUNT(*) as total_reports, COUNT(DISTINCT project_id) as active_projects, - COUNT(DISTINCT worker_id) as active_workers, + COUNT(DISTINCT user_id) as active_workers, SUM(CASE WHEN work_status_id = 2 THEN 1 ELSE 0 END) as error_reports, ROUND(AVG(work_hours), 2) as avg_hours_per_report FROM daily_work_reports @@ -47,7 +47,7 @@ class WorkAnalysis { report_date as date, SUM(work_hours) as hours, COUNT(*) as reports, - COUNT(DISTINCT worker_id) as workers, + COUNT(DISTINCT user_id) as workers, SUM(CASE WHEN work_status_id = 2 THEN 1 ELSE 0 END) as errors FROM daily_work_reports WHERE report_date BETWEEN ? AND ? @@ -73,7 +73,7 @@ class WorkAnalysis { async getWorkerStats(startDate, endDate) { const query = ` SELECT - dwr.worker_id, + dwr.user_id, w.worker_name, SUM(dwr.work_hours) as totalHours, COUNT(*) as totalReports, @@ -82,17 +82,17 @@ class WorkAnalysis { SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as errorCount, COUNT(DISTINCT dwr.report_date) as workingDays FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY dwr.worker_id, w.worker_name + GROUP BY dwr.user_id, w.worker_name ORDER BY totalHours DESC `; try { const [results] = await this.db.execute(query, [startDate, endDate]); return results.map(row => ({ - worker_id: row.worker_id, - worker_name: row.worker_name || `작업자 ${row.worker_id}`, + user_id: row.user_id, + worker_name: row.worker_name || `작업자 ${row.user_id}`, totalHours: parseFloat(row.totalHours) || 0, totalReports: parseInt(row.totalReports) || 0, avgHours: parseFloat(row.avgHours) || 0, @@ -114,7 +114,7 @@ class WorkAnalysis { p.project_name, SUM(dwr.work_hours) as totalHours, COUNT(*) as totalReports, - COUNT(DISTINCT dwr.worker_id) as workerCount, + COUNT(DISTINCT dwr.user_id) as workerCount, ROUND(AVG(dwr.work_hours), 2) as avgHours, SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as errorCount, COUNT(DISTINCT dwr.report_date) as activeDays @@ -152,7 +152,7 @@ class WorkAnalysis { SUM(dwr.work_hours) as totalHours, COUNT(*) as totalReports, ROUND(AVG(dwr.work_hours), 2) as avgHours, - COUNT(DISTINCT dwr.worker_id) as workerCount, + COUNT(DISTINCT dwr.user_id) as workerCount, SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as errorCount, COUNT(DISTINCT dwr.project_id) as projectCount FROM daily_work_reports dwr @@ -190,7 +190,7 @@ class WorkAnalysis { SELECT dwr.id, dwr.report_date, - dwr.worker_id, + dwr.user_id, w.worker_name, dwr.project_id, p.project_name, @@ -215,7 +215,7 @@ class WorkAnalysis { u.name as created_by_name, dwr.created_at FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id LEFT JOIN tasks t ON dwr.work_type_id = t.task_id @@ -234,8 +234,8 @@ class WorkAnalysis { return results.map(row => ({ id: row.id, report_date: row.report_date, - worker_id: row.worker_id, - worker_name: row.worker_name || `작업자 ${row.worker_id}`, + user_id: row.user_id, + worker_name: row.worker_name || `작업자 ${row.user_id}`, project_id: row.project_id, project_name: row.project_name || `프로젝트 ${row.project_id}`, job_no: row.job_no || 'N/A', @@ -274,7 +274,7 @@ class WorkAnalysis { SUM(work_hours) as total_hours, COUNT(*) as total_reports, ROUND(AVG(work_hours), 2) as avg_hours, - COUNT(DISTINCT worker_id) as active_workers + COUNT(DISTINCT user_id) as active_workers FROM daily_work_reports WHERE report_date BETWEEN ? AND ? GROUP BY DAYOFWEEK(report_date) @@ -306,7 +306,7 @@ class WorkAnalysis { irc.category_name as error_category_name, COUNT(*) as error_count, SUM(dwr.work_hours) as total_hours, - COUNT(DISTINCT dwr.worker_id) as affected_workers, + COUNT(DISTINCT dwr.user_id) as affected_workers, COUNT(DISTINCT dwr.project_id) as affected_projects FROM daily_work_reports dwr LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id @@ -341,7 +341,7 @@ class WorkAnalysis { MONTHNAME(report_date) as month_name, SUM(work_hours) as total_hours, COUNT(*) as total_reports, - COUNT(DISTINCT worker_id) as active_workers, + COUNT(DISTINCT user_id) as active_workers, COUNT(DISTINCT project_id) as active_projects, SUM(CASE WHEN work_status_id = 2 THEN 1 ELSE 0 END) as error_count FROM daily_work_reports @@ -371,7 +371,7 @@ class WorkAnalysis { async getWorkerSpecialization(startDate, endDate) { const query = ` SELECT - dwr.worker_id, + dwr.user_id, w.worker_name, dwr.work_type_id, wt.name as work_type_name, @@ -382,24 +382,24 @@ class WorkAnalysis { ROUND((SUM(dwr.work_hours) / ( SELECT SUM(work_hours) FROM daily_work_reports - WHERE worker_id = dwr.worker_id + WHERE user_id = dwr.user_id AND report_date BETWEEN ? AND ? )) * 100, 2) as percentage FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id LEFT JOIN projects p ON dwr.project_id = p.project_id WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY dwr.worker_id, w.worker_name, dwr.work_type_id, wt.name, dwr.project_id, p.project_name + GROUP BY dwr.user_id, w.worker_name, dwr.work_type_id, wt.name, dwr.project_id, p.project_name HAVING totalHours > 0 - ORDER BY dwr.worker_id, totalHours DESC + ORDER BY dwr.user_id, totalHours DESC `; try { const [results] = await this.db.execute(query, [startDate, endDate, startDate, endDate]); return results.map(row => ({ - worker_id: row.worker_id, - worker_name: row.worker_name || `작업자 ${row.worker_id}`, + user_id: row.user_id, + worker_name: row.worker_name || `작업자 ${row.user_id}`, work_type_id: row.work_type_id, work_type_name: row.work_type_name || `작업유형 ${row.work_type_id}`, project_id: row.project_id, diff --git a/system1-factory/api/models/analysisModel.js b/system1-factory/api/models/analysisModel.js index c0b5b84..067f3e0 100644 --- a/system1-factory/api/models/analysisModel.js +++ b/system1-factory/api/models/analysisModel.js @@ -26,7 +26,7 @@ const getAnalysis = async (startDate, endDate) => { const summarySql = ` SELECT COUNT(DISTINCT dwr.project_id) as totalProjects, - COUNT(DISTINCT dwr.worker_id) as totalworkers, + COUNT(DISTINCT dwr.user_id) as totalworkers, COUNT(DISTINCT dwr.task_id) as totalTasks, SUM(${workHoursCalc}) as totalHours FROM DailyWorkReports dwr @@ -35,7 +35,7 @@ const getAnalysis = async (startDate, endDate) => { // 2. 프로젝트별 집계 쿼리 const byProjectSql = ` - SELECT p.project_name as name, SUM(${workHoursCalc}) as hours, COUNT(DISTINCT dwr.worker_id) as participants + SELECT p.project_name as name, SUM(${workHoursCalc}) as hours, COUNT(DISTINCT dwr.user_id) as participants FROM DailyWorkReports dwr JOIN projects p ON dwr.project_id = p.project_id ${whereClause} @@ -48,7 +48,7 @@ const getAnalysis = async (startDate, endDate) => { const byWorkerSql = ` SELECT w.worker_name as name, SUM(${workHoursCalc}) as hours, COUNT(DISTINCT dwr.project_id) as participants FROM DailyWorkReports dwr - JOIN workers w ON dwr.worker_id = w.worker_id + JOIN workers w ON dwr.user_id = w.user_id ${whereClause} GROUP BY w.worker_name HAVING hours > 0 @@ -57,7 +57,7 @@ const getAnalysis = async (startDate, endDate) => { // 4. 작업별 집계 쿼리 const byTaskSql = ` - SELECT t.category as name, SUM(${workHoursCalc}) as hours, COUNT(DISTINCT dwr.worker_id) as participants + SELECT t.category as name, SUM(${workHoursCalc}) as hours, COUNT(DISTINCT dwr.user_id) as participants FROM DailyWorkReports dwr JOIN Tasks t ON dwr.task_id = t.task_id ${whereClause} @@ -74,7 +74,7 @@ const getAnalysis = async (startDate, endDate) => { (${workHoursCalc}) as work_hours, dwr.memo FROM DailyWorkReports dwr JOIN projects p ON dwr.project_id = p.project_id - JOIN workers w ON dwr.worker_id = w.worker_id + JOIN workers w ON dwr.user_id = w.user_id JOIN Tasks t ON dwr.task_id = t.task_id ${whereClause} HAVING work_hours > 0 diff --git a/system1-factory/api/models/attendanceModel.js b/system1-factory/api/models/attendanceModel.js index a14c411..c7f7761 100644 --- a/system1-factory/api/models/attendanceModel.js +++ b/system1-factory/api/models/attendanceModel.js @@ -2,7 +2,7 @@ const { getDb } = require('../dbPool'); class AttendanceModel { // 일일 근태 기록 조회 - static async getDailyAttendanceRecords(date, workerId = null) { + static async getDailyAttendanceRecords(date, userId = null) { const db = await getDb(); let query = ` SELECT @@ -15,7 +15,7 @@ class AttendanceModel { vt.type_code as vacation_type_code, vt.deduct_days as vacation_days FROM daily_attendance_records dar - LEFT JOIN workers w ON dar.worker_id = w.worker_id + LEFT JOIN workers w ON dar.user_id = w.user_id LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id WHERE dar.record_date = ? @@ -23,9 +23,9 @@ class AttendanceModel { const params = [date]; - if (workerId) { - query += ' AND dar.worker_id = ?'; - params.push(workerId); + if (userId) { + query += ' AND dar.user_id = ?'; + params.push(userId); } query += ' ORDER BY w.worker_name'; @@ -35,7 +35,7 @@ class AttendanceModel { } // 기간별 근태 기록 조회 (월별 조회용) - static async getDailyRecords(startDate, endDate, workerId = null) { + static async getDailyRecords(startDate, endDate, userId = null) { const db = await getDb(); let query = ` SELECT @@ -48,7 +48,7 @@ class AttendanceModel { vt.type_code as vacation_type_code, vt.deduct_days as vacation_days FROM daily_attendance_records dar - LEFT JOIN workers w ON dar.worker_id = w.worker_id + LEFT JOIN workers w ON dar.user_id = w.user_id LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id WHERE dar.record_date BETWEEN ? AND ? @@ -56,9 +56,9 @@ class AttendanceModel { const params = [startDate, endDate]; - if (workerId) { - query += ' AND dar.worker_id = ?'; - params.push(workerId); + if (userId) { + query += ' AND dar.user_id = ?'; + params.push(userId); } query += ' ORDER BY dar.record_date ASC'; @@ -68,17 +68,17 @@ class AttendanceModel { } // 작업 보고서와 근태 기록 동기화 (시간 합산 및 상태 업데이트) - static async syncWithWorkReports(workerId, date) { + static async syncWithWorkReports(userId, date) { const db = await getDb(); // 1. 해당 날짜의 총 작업 시간 계산 const [reportStats] = await db.execute(` - SELECT + SELECT COALESCE(SUM(work_hours), 0) as total_hours, COUNT(*) as report_count FROM daily_work_reports - WHERE worker_id = ? AND report_date = ? - `, [workerId, date]); + WHERE user_id = ? AND report_date = ? + `, [userId, date]); const totalHours = parseFloat(reportStats[0].total_hours || 0); const reportCount = reportStats[0].report_count; @@ -110,8 +110,8 @@ class AttendanceModel { // 3. 기록 업데이트 (휴가 정보는 유지) // 기존 기록 조회 const [existing] = await db.execute( - 'SELECT id, vacation_type_id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?', - [workerId, date] + 'SELECT id, vacation_type_id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?', + [userId, date] ); if (existing.length > 0) { @@ -138,9 +138,9 @@ class AttendanceModel { // 생성자가 명확하지 않으므로 시스템(1) 또는 알 수 없음 처리 await db.execute(` INSERT INTO daily_attendance_records - (record_date, worker_id, total_work_hours, attendance_type_id, status, created_by) + (record_date, user_id, total_work_hours, attendance_type_id, status, created_by) VALUES (?, ?, ?, ?, ?, 1) - `, [date, workerId, totalHours, typeId, status]); + `, [date, userId, totalHours, typeId, status]); return { synced: true, totalHours, status, created: true }; } @@ -152,14 +152,14 @@ class AttendanceModel { // 1. 활성 작업자 조회 const [workers] = await db.execute( - 'SELECT worker_id FROM workers WHERE status = "active"' // is_active check not needed as status covers it based on previous fix? Wait, previous fix used status='active'. + 'SELECT user_id FROM workers WHERE status = "active" AND user_id IS NOT NULL' ); if (workers.length === 0) return { inserted: 0 }; // 2. 일일 근태 레코드 일괄 생성 (이미 존재하면 무시) // VALUES (...), (...), ... - const values = workers.map(w => [date, w.worker_id, 'incomplete', createdBy]); + const values = workers.map(w => [date, w.user_id, 'incomplete', createdBy]); // Bulk INSERT IGNORE // Note: mysql2 execute doesn't support nested arrays for bulk insert easily with placeholder ? @@ -175,10 +175,10 @@ class AttendanceModel { for (const w of workers) { const [result] = await conn.execute(` - INSERT IGNORE INTO daily_attendance_records - (record_date, worker_id, status, created_by) + INSERT IGNORE INTO daily_attendance_records + (record_date, user_id, status, created_by) VALUES (?, ?, 'incomplete', ?) - `, [date, w.worker_id, createdBy]); + `, [date, w.user_id, createdBy]); insertedCount += result.affectedRows; } @@ -200,7 +200,7 @@ class AttendanceModel { const { record_date, - worker_id, + user_id, total_work_hours = 8, work_attendance_type_id = 1, vacation_type_id = null, @@ -212,8 +212,8 @@ class AttendanceModel { // 기존 기록 확인 const [existing] = await db.execute( - 'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?', - [worker_id, record_date] + 'SELECT id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?', + [user_id, record_date] ); if (existing.length > 0) { @@ -240,12 +240,12 @@ class AttendanceModel { // 생성 const [result] = await db.execute(` INSERT INTO daily_attendance_records ( - record_date, worker_id, total_work_hours, attendance_type_id, + record_date, user_id, total_work_hours, attendance_type_id, vacation_type_id, is_overtime_approved, created_by ) VALUES (?, ?, ?, ?, ?, ?, ?) `, [ record_date, - worker_id, + user_id, total_work_hours, attendance_type_id, vacation_type_id, @@ -263,8 +263,8 @@ class AttendanceModel { // 모든 작업자와 해당 날짜의 근태 기록을 조회 const [rows] = await db.execute(` - SELECT - w.worker_id, + SELECT + w.user_id, w.worker_name, w.job_type, COALESCE(dar.total_work_hours, 0) as total_work_hours, @@ -277,16 +277,16 @@ class AttendanceModel { vt.type_code as vacation_type_code, dar.notes, -- 작업 건수 계산 - (SELECT COUNT(*) FROM daily_work_reports dwr - WHERE dwr.worker_id = w.worker_id AND dwr.report_date = ?) as work_count, + (SELECT COUNT(*) FROM daily_work_reports dwr + WHERE dwr.user_id = w.user_id AND dwr.report_date = ?) as work_count, -- 오류 건수 계산 - (SELECT COUNT(*) FROM daily_work_reports dwr - WHERE dwr.worker_id = w.worker_id AND dwr.report_date = ? AND dwr.work_status_id = 2) as error_count + (SELECT COUNT(*) FROM daily_work_reports dwr + WHERE dwr.user_id = w.user_id AND dwr.report_date = ? AND dwr.work_status_id = 2) as error_count FROM workers w - LEFT JOIN daily_attendance_records dar ON w.worker_id = dar.worker_id AND dar.record_date = ? + LEFT JOIN daily_attendance_records dar ON w.user_id = dar.user_id AND dar.record_date = ? LEFT JOIN work_attendance_types wat ON dar.attendance_type_id = wat.id LEFT JOIN vacation_types vt ON dar.vacation_type_id = vt.id - WHERE w.is_active = TRUE + WHERE w.status = 'active' AND w.user_id IS NOT NULL ORDER BY w.worker_name `, [date, date, date]); @@ -294,7 +294,7 @@ class AttendanceModel { } // 휴가 처리 - static async processVacation(workerId, date, vacationType, createdBy) { + static async processVacation(userId, date, vacationType, createdBy) { const db = await getDb(); // 휴가 유형 정보 조회 @@ -312,9 +312,9 @@ class AttendanceModel { // 현재 작업 시간 조회 const [workHours] = await db.execute(` SELECT COALESCE(SUM(work_hours), 0) as total_hours - FROM daily_work_reports - WHERE worker_id = ? AND report_date = ? - `, [workerId, date]); + FROM daily_work_reports + WHERE user_id = ? AND report_date = ? + `, [userId, date]); const currentHours = parseFloat(workHours[0].total_hours); // deduct_days를 시간으로 변환 (1일 = 8시간) @@ -338,12 +338,12 @@ class AttendanceModel { // 휴가 작업 기록 생성 (프로젝트 ID 13 = "연차/휴무", work_type_id 1 = 기본) await db.execute(` INSERT INTO daily_work_reports ( - report_date, worker_id, project_id, work_type_id, work_status_id, + report_date, user_id, project_id, work_type_id, work_status_id, work_hours, description, created_by ) VALUES (?, ?, 13, 1, 1, ?, ?, ?) `, [ date, - workerId, + userId, vacationHours, `${vacationTypeInfo.type_name} 처리`, createdBy @@ -352,7 +352,7 @@ class AttendanceModel { // 근태 기록 업데이트 const attendanceData = { record_date: date, - worker_id: workerId, + user_id: userId, total_work_hours: totalHours, work_attendance_type_id: attendanceTypes[0]?.id, vacation_type_id: vacationTypeInfo.id, @@ -364,19 +364,19 @@ class AttendanceModel { } // 초과근무 승인 - static async approveOvertime(workerId, date, approvedBy) { + static async approveOvertime(userId, date, approvedBy) { const db = await getDb(); const [result] = await db.execute(` - UPDATE daily_attendance_records - SET + UPDATE daily_attendance_records + SET overtime_approved = TRUE, overtime_approved_by = ?, overtime_approved_at = CURRENT_TIMESTAMP, updated_by = ?, updated_at = CURRENT_TIMESTAMP - WHERE worker_id = ? AND record_date = ? - `, [approvedBy, approvedBy, workerId, date]); + WHERE user_id = ? AND record_date = ? + `, [approvedBy, approvedBy, userId, date]); return result.affectedRows > 0; } @@ -400,24 +400,24 @@ class AttendanceModel { } // 작업자 휴가 잔여 조회 - static async getWorkerVacationBalance(workerId, year = null) { + static async getWorkerVacationBalance(userId, year = null) { const db = await getDb(); const currentYear = year || new Date().getFullYear(); const [rows] = await db.execute(` - SELECT id, worker_id, year, total_annual_leave, used_annual_leave, notes, created_at, updated_at FROM worker_vacation_balance - WHERE worker_id = ? AND year = ? - `, [workerId, currentYear]); + SELECT id, user_id, year, total_annual_leave, used_annual_leave, notes, created_at, updated_at FROM worker_vacation_balance + WHERE user_id = ? AND year = ? + `, [userId, currentYear]); if (rows.length === 0) { // 기본 연차 생성 (15일) await db.execute(` - INSERT INTO worker_vacation_balance (worker_id, year, total_annual_leave) + INSERT INTO worker_vacation_balance (user_id, year, total_annual_leave) VALUES (?, ?, 15.0) - `, [workerId, currentYear]); + `, [userId, currentYear]); return { - worker_id: workerId, + user_id: userId, year: currentYear, total_annual_leave: 15.0, used_annual_leave: 0, @@ -429,14 +429,14 @@ class AttendanceModel { } // 월별 근태 통계 - static async getMonthlyAttendanceStats(year, month, workerId = null) { + static async getMonthlyAttendanceStats(year, month, userId = null) { const db = await getDb(); // work_attendance_types: 1=NORMAL, 2=LATE, 3=EARLY_LEAVE, 4=ABSENT, 5=VACATION // vacation_types: 1=ANNUAL(연차), 2=HALF_ANNUAL(반차), 3=SICK(병가), 4=SPECIAL(경조사) let query = ` SELECT - w.worker_id, + w.user_id, w.worker_name, COUNT(CASE WHEN dar.attendance_type_id = 1 AND (dar.is_overtime_approved = 0 OR dar.is_overtime_approved IS NULL) THEN 1 END) as regular_days, COUNT(CASE WHEN dar.is_overtime_approved = 1 OR dar.total_work_hours > 8 THEN 1 END) as overtime_days, @@ -446,19 +446,19 @@ class AttendanceModel { COALESCE(SUM(dar.total_work_hours), 0) as total_work_hours, COALESCE(AVG(dar.total_work_hours), 0) as avg_work_hours FROM workers w - LEFT JOIN daily_attendance_records dar ON w.worker_id = dar.worker_id + LEFT JOIN daily_attendance_records dar ON w.user_id = dar.user_id AND YEAR(dar.record_date) = ? AND MONTH(dar.record_date) = ? WHERE w.employment_status = 'employed' `; const params = [year, month]; - if (workerId) { - query += ' AND w.worker_id = ?'; - params.push(workerId); + if (userId) { + query += ' AND w.user_id = ?'; + params.push(userId); } - query += ' GROUP BY w.worker_id, w.worker_name ORDER BY w.worker_name'; + query += ' GROUP BY w.user_id, w.worker_name ORDER BY w.worker_name'; const [rows] = await db.execute(query, params); return rows; @@ -467,12 +467,12 @@ class AttendanceModel { // 출근 체크 기록 생성 또는 업데이트 static async upsertCheckin(checkinData) { const db = await getDb(); - const { worker_id, record_date, is_present } = checkinData; + const { user_id, record_date, is_present } = checkinData; // 해당 날짜에 기록이 있는지 확인 const [existing] = await db.execute( - 'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?', - [worker_id, record_date] + 'SELECT id FROM daily_attendance_records WHERE user_id = ? AND record_date = ?', + [user_id, record_date] ); if (existing.length > 0) { @@ -486,9 +486,9 @@ class AttendanceModel { // 새로 생성 (기본값으로) const [result] = await db.execute( `INSERT INTO daily_attendance_records - (worker_id, record_date, is_present, attendance_type_id, created_by) + (user_id, record_date, is_present, attendance_type_id, created_by) VALUES (?, ?, ?, 1, 1)`, - [worker_id, record_date, is_present] + [user_id, record_date, is_present] ); return result.insertId; } @@ -499,7 +499,7 @@ class AttendanceModel { const db = await getDb(); const query = ` SELECT - w.worker_id, + w.user_id, w.worker_name, w.job_type, w.employment_status, @@ -512,9 +512,9 @@ class AttendanceModel { vr.days_used as vacation_days FROM workers w LEFT JOIN daily_attendance_records dar - ON w.worker_id = dar.worker_id AND dar.record_date = ? + ON w.user_id = dar.user_id AND dar.record_date = ? LEFT JOIN vacation_requests vr - ON w.worker_id = vr.worker_id + ON w.user_id = vr.user_id AND ? BETWEEN vr.start_date AND vr.end_date AND vr.status = 'approved' LEFT JOIN vacation_types vt ON vr.vacation_type_id = vt.id diff --git a/system1-factory/api/models/dailyIssueReportModel.js b/system1-factory/api/models/dailyIssueReportModel.js index 103ad14..2e193de 100644 --- a/system1-factory/api/models/dailyIssueReportModel.js +++ b/system1-factory/api/models/dailyIssueReportModel.js @@ -12,13 +12,13 @@ const createMany = async (reports) => { const insertedIds = []; const sql = ` INSERT INTO DailyIssueReports - (date, worker_id, project_id, start_time, end_time, issue_type_id) + (date, user_id, project_id, start_time, end_time, issue_type_id) VALUES (?, ?, ?, ?, ?, ?) `; for (const report of reports) { - const { date, worker_id, project_id, start_time, end_time, issue_type_id } = report; - const [result] = await conn.query(sql, [date, worker_id, project_id, start_time, end_time, issue_type_id]); + const { date, user_id, project_id, start_time, end_time, issue_type_id } = report; + const [result] = await conn.query(sql, [date, user_id, project_id, start_time, end_time, issue_type_id]); insertedIds.push(result.insertId); } @@ -43,7 +43,7 @@ const getAllByDate = async (date) => { d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time, t.category, t.subcategory, d.description FROM DailyIssueReports d - LEFT JOIN workers w ON d.worker_id = w.worker_id + LEFT JOIN workers w ON d.user_id = w.user_id LEFT JOIN projects p ON d.project_id = p.project_id LEFT JOIN IssueTypes t ON d.issue_type_id = t.issue_type_id WHERE d.date = ? @@ -58,7 +58,7 @@ const getAllByDate = async (date) => { */ const getById = async (id) => { const db = await getDb(); - const [rows] = await db.query(`SELECT id, date, worker_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]); + const [rows] = await db.query(`SELECT id, date, user_id, project_id, issue_type_id, description, created_at, start_time, end_time FROM DailyIssueReports WHERE id = ?`, [id]); return rows[0]; }; diff --git a/system1-factory/api/models/dailyWorkReportModel.js b/system1-factory/api/models/dailyWorkReportModel.js index 04fb63d..2d1d86c 100644 --- a/system1-factory/api/models/dailyWorkReportModel.js +++ b/system1-factory/api/models/dailyWorkReportModel.js @@ -39,22 +39,22 @@ const getAllErrorTypes = async () => { * 누적 추가 전용 함수 (createDailyReport 대체) - 절대 삭제 안함! */ const createDailyReport = async (reportData) => { - const { report_date, worker_id, work_entries, created_by, created_by_name, total_hours } = reportData; + const { report_date, user_id, work_entries, created_by, created_by_name, total_hours } = reportData; const db = await getDb(); const conn = await db.getConnection(); try { await conn.beginTransaction(); - console.log(`${created_by_name}이 ${report_date} ${worker_id}번 작업자에게 데이터 추가 중...`); + console.log(`${created_by_name}이 ${report_date} ${user_id}번 작업자에게 데이터 추가 중...`); const [existingReports] = await conn.query( `SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours FROM daily_work_reports dwr - LEFT JOIN users u ON dwr.created_by = u.user_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? + LEFT JOIN sso_users u ON dwr.created_by = u.user_id + WHERE dwr.report_date = ? AND dwr.user_id = ? GROUP BY dwr.created_by`, - [report_date, worker_id] + [report_date, user_id] ); console.log('기존 데이터 (삭제하지 않음):', existingReports); @@ -66,9 +66,9 @@ const createDailyReport = async (reportData) => { const [insertResult] = await conn.query( `INSERT INTO daily_work_reports - (report_date, worker_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_by, created_at) + (report_date, user_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())`, - [report_date, worker_id, project_id, work_type_id, work_status_id || 1, error_type_id || null, work_hours, created_by] + [report_date, user_id, project_id, work_type_id, work_status_id || 1, error_type_id || null, work_hours, created_by] ); insertedIds.push(insertResult.insertId); @@ -77,10 +77,10 @@ const createDailyReport = async (reportData) => { const [finalReports] = await conn.query( `SELECT dwr.created_by, u.name as created_by_name, COUNT(*) as count, SUM(dwr.work_hours) as total_hours FROM daily_work_reports dwr - LEFT JOIN users u ON dwr.created_by = u.user_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? + LEFT JOIN sso_users u ON dwr.created_by = u.user_id + WHERE dwr.report_date = ? AND dwr.user_id = ? GROUP BY dwr.created_by`, - [report_date, worker_id] + [report_date, user_id] ); const grandTotal = finalReports.reduce((sum, report) => sum + parseFloat(report.total_hours || 0), 0); @@ -103,7 +103,7 @@ const createDailyReport = async (reportData) => { insertedIds[0] || null, JSON.stringify({ report_date, - worker_id, + user_id, work_entries_count: work_entries.length, added_hours: total_hours, my_total: myTotal, @@ -123,7 +123,7 @@ const createDailyReport = async (reportData) => { // 근태 기록 동기화 try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패:', syncErr); } @@ -153,7 +153,7 @@ const createDailyReport = async (reportData) => { /** * 특정 날짜 + 작업자 + 작성자의 누적 현황 조회 */ -const getMyAccumulatedHours = async (date, worker_id, created_by) => { +const getMyAccumulatedHours = async (date, user_id, created_by) => { const db = await getDb(); const sql = ` @@ -166,32 +166,32 @@ const getMyAccumulatedHours = async (date, worker_id, created_by) => { ) as my_entries FROM daily_work_reports dwr LEFT JOIN projects p ON dwr.project_id = p.project_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? AND dwr.created_by = ? + WHERE dwr.report_date = ? AND dwr.user_id = ? AND dwr.created_by = ? `; - const [rows] = await db.query(sql, [date, worker_id, created_by]); + const [rows] = await db.query(sql, [date, user_id, created_by]); return rows[0] || { my_total_hours: 0, my_entry_count: 0, my_entries: null }; }; /** * 누적 현황 조회 - 날짜+작업자별 (모든 기여자) */ -const getAccumulatedReportsByDate = async (date, worker_id) => { +const getAccumulatedReportsByDate = async (date, user_id) => { const db = await getDb(); const sql = getSelectQuery() + ` - WHERE dwr.report_date = ? AND dwr.worker_id = ? + WHERE dwr.report_date = ? AND dwr.user_id = ? ORDER BY dwr.created_by, dwr.created_at ASC `; - const [rows] = await db.query(sql, [date, worker_id]); + const [rows] = await db.query(sql, [date, user_id]); return rows; }; /** * 기여자별 요약 조회 */ -const getContributorsByDate = async (date, worker_id) => { +const getContributorsByDate = async (date, user_id) => { const db = await getDb(); const sql = ` @@ -207,14 +207,14 @@ const getContributorsByDate = async (date, worker_id) => { ORDER BY dwr.created_at SEPARATOR ', ' ) as entry_details FROM daily_work_reports dwr - LEFT JOIN users u ON dwr.created_by = u.user_id + LEFT JOIN sso_users u ON dwr.created_by = u.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id - WHERE dwr.report_date = ? AND dwr.worker_id = ? + WHERE dwr.report_date = ? AND dwr.user_id = ? GROUP BY dwr.created_by ORDER BY total_hours DESC, first_entry ASC `; - const [rows] = await db.query(sql, [date, worker_id]); + const [rows] = await db.query(sql, [date, user_id]); return rows; }; @@ -232,9 +232,9 @@ const removeSpecificEntry = async (entry_id, deleted_by) => { const [entryInfo] = await conn.query( `SELECT dwr.*, w.worker_name, p.project_name, u.name as created_by_name FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id - LEFT JOIN users u ON dwr.created_by = u.user_id + LEFT JOIN sso_users u ON dwr.created_by = u.user_id WHERE dwr.id = ?`, [entry_id] ); @@ -282,7 +282,7 @@ const getSelectQuery = () => ` SELECT dwr.id, dwr.report_date, - dwr.worker_id, + dwr.user_id, dwr.project_id, dwr.work_type_id, dwr.work_status_id, @@ -299,13 +299,13 @@ const getSelectQuery = () => ` dwr.created_at, dwr.updated_at FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id LEFT JOIN work_status_types wst ON dwr.work_status_id = wst.id LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id - LEFT JOIN users u ON dwr.created_by = u.user_id + LEFT JOIN sso_users u ON dwr.created_by = u.user_id `; /** @@ -347,26 +347,26 @@ const getByDateAndCreator = async (date, created_by) => { /** * 10. 일일 작업보고서 조회 (작업자별) */ -const getByWorker = async (worker_id) => { +const getByWorker = async (user_id) => { const db = await getDb(); const sql = getSelectQuery() + ` - WHERE dwr.worker_id = ? + WHERE dwr.user_id = ? ORDER BY dwr.report_date DESC, dwr.id ASC `; - const [rows] = await db.query(sql, [worker_id]); + const [rows] = await db.query(sql, [user_id]); return rows; }; /** * 11. 일일 작업보고서 조회 (날짜 + 작업자) */ -const getByDateAndWorker = async (date, worker_id) => { +const getByDateAndWorker = async (date, user_id) => { const db = await getDb(); const sql = getSelectQuery() + ` - WHERE dwr.report_date = ? AND dwr.worker_id = ? + WHERE dwr.report_date = ? AND dwr.user_id = ? ORDER BY dwr.id ASC `; - const [rows] = await db.query(sql, [date, worker_id]); + const [rows] = await db.query(sql, [date, user_id]); return rows; }; @@ -387,7 +387,7 @@ const getByRange = async (start_date, end_date) => { * 13. 상세 검색 (페이지네이션 포함) */ const searchWithDetails = async (params) => { - const { start_date, end_date, worker_id, project_id, work_status_id, created_by, page, limit } = params; + const { start_date, end_date, user_id, project_id, work_status_id, created_by, page, limit } = params; const db = await getDb(); @@ -395,9 +395,9 @@ const searchWithDetails = async (params) => { let whereConditions = ['dwr.report_date BETWEEN ? AND ?']; let queryParams = [start_date, end_date]; - if (worker_id) { - whereConditions.push('dwr.worker_id = ?'); - queryParams.push(worker_id); + if (user_id) { + whereConditions.push('dwr.user_id = ?'); + queryParams.push(user_id); } if (project_id) { @@ -448,16 +448,16 @@ const getSummaryByDate = async (date) => { const sql = ` SELECT - dwr.worker_id, + dwr.user_id, w.worker_name, dwr.report_date, SUM(dwr.work_hours) as total_hours, COUNT(*) as work_entries_count, SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id WHERE dwr.report_date = ? - GROUP BY dwr.worker_id, dwr.report_date + GROUP BY dwr.user_id, dwr.report_date ORDER BY w.worker_name ASC `; const [rows] = await db.query(sql, [date]); @@ -467,24 +467,24 @@ const getSummaryByDate = async (date) => { /** * 15. 일일 근무 요약 조회 (작업자별) */ -const getSummaryByWorker = async (worker_id) => { +const getSummaryByWorker = async (user_id) => { const db = await getDb(); const sql = ` SELECT dwr.report_date, - dwr.worker_id, + dwr.user_id, w.worker_name, SUM(dwr.work_hours) as total_hours, COUNT(*) as work_entries_count, SUM(CASE WHEN dwr.work_status_id = 2 THEN 1 ELSE 0 END) as error_count FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id - WHERE dwr.worker_id = ? - GROUP BY dwr.report_date, dwr.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id + WHERE dwr.user_id = ? + GROUP BY dwr.report_date, dwr.user_id ORDER BY dwr.report_date DESC `; - const [rows] = await db.query(sql, [worker_id]); + const [rows] = await db.query(sql, [user_id]); return rows; }; @@ -499,7 +499,7 @@ const getMonthlySummary = async (year, month) => { const sql = ` SELECT dwr.report_date, - dwr.worker_id, + dwr.user_id, w.worker_name, SUM(dwr.work_hours) as total_work_hours, COUNT(DISTINCT dwr.project_id) as project_count, @@ -508,11 +508,11 @@ const getMonthlySummary = async (year, month) => { GROUP_CONCAT(DISTINCT p.project_name ORDER BY p.project_name) as projects, GROUP_CONCAT(DISTINCT wt.name ORDER BY wt.name) as work_types FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY dwr.report_date, dwr.worker_id + GROUP BY dwr.report_date, dwr.user_id ORDER BY dwr.report_date DESC, w.worker_name ASC `; const [rows] = await db.query(sql, [start, end]); @@ -567,11 +567,11 @@ const updateById = async (id, updateData) => { // [Sync] 근태 기록 동기화 try { - const [targetReport] = await db.query('SELECT worker_id, report_date FROM daily_work_reports WHERE id = ?', [id]); + const [targetReport] = await db.query('SELECT user_id, report_date FROM daily_work_reports WHERE id = ?', [id]); if (targetReport.length > 0) { - const { worker_id, report_date } = targetReport[0]; + const { user_id, report_date } = targetReport[0]; const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } } catch (syncErr) { console.error('근태 기록 동기화 실패 (Update):', syncErr); @@ -615,9 +615,9 @@ const removeById = async (id, deletedBy) => { // [Sync] 근태 기록 동기화 if (reportInfo.length > 0) { try { - const { worker_id, report_date } = reportInfo[0]; + const { user_id, report_date } = reportInfo[0]; const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (Delete):', syncErr); } @@ -635,7 +635,7 @@ const removeById = async (id, deletedBy) => { /** * 19. 작업자의 특정 날짜 전체 삭제 */ -const removeByDateAndWorker = async (date, worker_id, deletedBy) => { +const removeByDateAndWorker = async (date, user_id, deletedBy) => { const db = await getDb(); const conn = await db.getConnection(); @@ -644,14 +644,14 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy) => { // 삭제 전 정보 저장 (감사 로그용) const [reportInfos] = await conn.query( - 'SELECT id, report_date, worker_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_at, updated_at, created_by, updated_by FROM daily_work_reports WHERE report_date = ? AND worker_id = ?', - [date, worker_id] + 'SELECT id, report_date, user_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_at, updated_at, created_by, updated_by FROM daily_work_reports WHERE report_date = ? AND user_id = ?', + [date, user_id] ); // 작업보고서 삭제 const [result] = await conn.query( - 'DELETE FROM daily_work_reports WHERE report_date = ? AND worker_id = ?', - [date, worker_id] + 'DELETE FROM daily_work_reports WHERE report_date = ? AND user_id = ?', + [date, user_id] ); // 감사 로그 추가 @@ -673,7 +673,7 @@ const removeByDateAndWorker = async (date, worker_id, deletedBy) => { // [Sync] 근태 기록 동기화 try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, date); + await AttendanceModel.syncWithWorkReports(user_id, date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (Batch Delete):', syncErr); } @@ -697,7 +697,7 @@ const getStatistics = async (start_date, end_date) => { SELECT COUNT(*) as total_reports, SUM(work_hours) as total_hours, - COUNT(DISTINCT worker_id) as unique_workers, + COUNT(DISTINCT user_id) as unique_workers, COUNT(DISTINCT project_id) as unique_projects FROM daily_work_reports WHERE report_date BETWEEN ? AND ? @@ -708,7 +708,7 @@ const getStatistics = async (start_date, end_date) => { SELECT report_date, SUM(work_hours) as daily_hours, - COUNT(DISTINCT worker_id) as daily_workers + COUNT(DISTINCT user_id) as daily_workers FROM daily_work_reports WHERE report_date BETWEEN ? AND ? GROUP BY report_date @@ -727,7 +727,7 @@ const getStatistics = async (start_date, end_date) => { * @param {object} modelData - 서비스 레이어에서 전달된 데이터 * @returns {Promise} 삽입된 항목의 ID 배열과 개수 */ -const createReportEntries = async ({ report_date, worker_id, entries }) => { +const createReportEntries = async ({ report_date, user_id, entries }) => { const db = await getDb(); const conn = await db.getConnection(); try { @@ -736,7 +736,7 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => { const insertedIds = []; const sql = ` INSERT INTO daily_work_reports - (report_date, worker_id, project_id, work_type_id, work_hours, work_status_id, error_type_id, created_by) + (report_date, user_id, project_id, work_type_id, work_hours, work_status_id, error_type_id, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; @@ -744,7 +744,7 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => { const { project_id, work_type_id, work_hours, work_status_id, error_type_id, created_by } = entry; const [result] = await conn.query(sql, [ report_date, - worker_id, + user_id, project_id, work_type_id, work_hours, @@ -760,7 +760,7 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => { // [Sync] 근태 기록 동기화 try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (V2 Create):', syncErr); } @@ -788,7 +788,7 @@ const getSelectQueryV2 = () => ` SELECT dwr.id, dwr.report_date, - dwr.worker_id, + dwr.user_id, dwr.project_id, dwr.work_type_id, dwr.work_status_id, @@ -805,19 +805,19 @@ const getSelectQueryV2 = () => ` u.name as created_by_name, dwr.created_at FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN tasks t ON dwr.work_type_id = t.task_id LEFT JOIN work_types wt ON t.work_type_id = wt.id LEFT JOIN work_status_types wst ON dwr.work_status_id = wst.id LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id - LEFT JOIN users u ON dwr.created_by = u.user_id + LEFT JOIN sso_users u ON dwr.created_by = u.user_id `; /** * [V2] 옵션 기반으로 작업 보고서를 조회합니다. - * @param {object} options - 조회 조건 (date, worker_id, created_by_user_id 등) + * @param {object} options - 조회 조건 (date, user_id, created_by_user_id 등) * @returns {Promise} 조회된 작업 보고서 배열 */ const getReportsWithOptions = async (options) => { @@ -834,9 +834,9 @@ const getReportsWithOptions = async (options) => { queryParams.push(options.start_date, options.end_date); } - if (options.worker_id) { - whereConditions.push('dwr.worker_id = ?'); - queryParams.push(options.worker_id); + if (options.user_id) { + whereConditions.push('dwr.user_id = ?'); + queryParams.push(options.user_id); } if (options.created_by_user_id) { whereConditions.push('dwr.created_by = ?'); @@ -890,7 +890,7 @@ const updateReportById = async (reportId, updateData) => { // [Sync] 업데이트 전 정보 조회 (동기화를 위해) let targetInfo = null; try { - const [rows] = await db.query('SELECT worker_id, report_date FROM daily_work_reports WHERE id = ?', [reportId]); + const [rows] = await db.query('SELECT user_id, report_date FROM daily_work_reports WHERE id = ?', [reportId]); if (rows.length > 0) targetInfo = rows[0]; } catch (e) { console.warn('Sync fetch failed', e); } @@ -902,7 +902,7 @@ const updateReportById = async (reportId, updateData) => { if (targetInfo) { try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(targetInfo.worker_id, targetInfo.report_date); + await AttendanceModel.syncWithWorkReports(targetInfo.user_id, targetInfo.report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (V2 Update):', syncErr); } @@ -925,7 +925,7 @@ const removeReportById = async (reportId, deletedByUserId) => { await conn.beginTransaction(); // 감사 로그를 위해 삭제 전 정보 조회 - const [reportInfo] = await conn.query('SELECT id, report_date, worker_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_at, updated_at, created_by, updated_by FROM daily_work_reports WHERE id = ?', [reportId]); + const [reportInfo] = await conn.query('SELECT id, report_date, user_id, project_id, work_type_id, work_status_id, error_type_id, work_hours, created_at, updated_at, created_by, updated_by FROM daily_work_reports WHERE id = ?', [reportId]); // 실제 삭제 작업 const [result] = await conn.query('DELETE FROM daily_work_reports WHERE id = ?', [reportId]); @@ -940,9 +940,9 @@ const removeReportById = async (reportId, deletedByUserId) => { // [Sync] 근태 기록 동기화 if (reportInfo.length > 0) { try { - const { worker_id, report_date } = reportInfo[0]; + const { user_id, report_date } = reportInfo[0]; const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (V2 Delete):', syncErr); } @@ -1075,7 +1075,7 @@ const createFromTbmAssignment = async (reportData) => { const { tbm_assignment_id, tbm_session_id, - worker_id, + user_id, project_id, work_type_id, report_date, @@ -1098,7 +1098,7 @@ const createFromTbmAssignment = async (reportData) => { // 1. 작업보고서 생성 const sql = ` INSERT INTO daily_work_reports - (tbm_session_id, tbm_assignment_id, report_date, worker_id, project_id, work_type_id, + (tbm_session_id, tbm_assignment_id, report_date, user_id, project_id, work_type_id, start_time, end_time, work_hours, total_hours, regular_hours, error_hours, work_status_id, error_type_id, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) @@ -1108,7 +1108,7 @@ const createFromTbmAssignment = async (reportData) => { tbm_session_id, tbm_assignment_id, report_date, - worker_id, + user_id, project_id, work_type_id, start_time || null, @@ -1150,7 +1150,7 @@ const createFromTbmAssignment = async (reportData) => { // 4. 근태 기록 동기화 try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(worker_id, report_date); + await AttendanceModel.syncWithWorkReports(user_id, report_date); } catch (syncErr) { console.error('근태 기록 동기화 실패 (TBM Report):', syncErr); } diff --git a/system1-factory/api/models/departmentModel.js b/system1-factory/api/models/departmentModel.js index e9c329b..bfe7e8c 100644 --- a/system1-factory/api/models/departmentModel.js +++ b/system1-factory/api/models/departmentModel.js @@ -91,7 +91,7 @@ const departmentModel = { SELECT w.*, d.department_name, u.user_id, u.username FROM workers w LEFT JOIN departments d ON w.department_id = d.department_id - LEFT JOIN users u ON u.worker_id = w.worker_id + LEFT JOIN users u ON u.user_id = w.user_id WHERE w.department_id = ? ORDER BY w.worker_name `, [departmentId]); @@ -99,20 +99,20 @@ const departmentModel = { }, // 작업자 부서 변경 - async moveWorker(workerId, departmentId) { + async moveWorker(userId, departmentId) { const db = await getDb(); const [result] = await db.query(` - UPDATE workers SET department_id = ? WHERE worker_id = ? - `, [departmentId, workerId]); + UPDATE workers SET department_id = ? WHERE user_id = ? + `, [departmentId, userId]); return result.affectedRows > 0; }, // 여러 작업자 부서 일괄 변경 - async moveWorkers(workerIds, departmentId) { + async moveWorkers(userIds, departmentId) { const db = await getDb(); const [result] = await db.query(` - UPDATE workers SET department_id = ? WHERE worker_id IN (?) - `, [departmentId, workerIds]); + UPDATE workers SET department_id = ? WHERE user_id IN (?) + `, [departmentId, userIds]); return result.affectedRows; } }; diff --git a/system1-factory/api/models/monthlyStatusModel.js b/system1-factory/api/models/monthlyStatusModel.js index d7e86e4..f40df94 100644 --- a/system1-factory/api/models/monthlyStatusModel.js +++ b/system1-factory/api/models/monthlyStatusModel.js @@ -49,7 +49,7 @@ class MonthlyStatusModel { // daily_work_reports에서 직접 집계하여 조회 (중복 없음 보장) const [rows] = await db.query(` SELECT - w.worker_id, + w.user_id, w.worker_name, w.job_type, YEAR(?) as year, @@ -77,9 +77,9 @@ class MonthlyStatusModel { END as has_issues, MAX(dwr.created_at) as last_updated FROM workers w - LEFT JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id AND dwr.report_date = ? + LEFT JOIN daily_work_reports dwr ON w.user_id = dwr.user_id AND dwr.report_date = ? WHERE w.status = 'active' - GROUP BY w.worker_id, w.worker_name, w.job_type + GROUP BY w.user_id, w.worker_name, w.job_type ORDER BY w.worker_name ASC `, [date, date, date, date]); @@ -97,15 +97,15 @@ class MonthlyStatusModel { try { // 해당 월의 모든 날짜와 작업자 조합을 찾아서 재계산 const [workDates] = await db.execute(` - SELECT DISTINCT report_date, worker_id - FROM daily_work_reports + SELECT DISTINCT report_date, user_id + FROM daily_work_reports WHERE YEAR(report_date) = ? AND MONTH(report_date) = ? `, [year, month]); let updatedCount = 0; - for (const { report_date, worker_id } of workDates) { - await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [report_date, worker_id]); + for (const { report_date, user_id } of workDates) { + await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [report_date, user_id]); updatedCount++; } @@ -119,23 +119,23 @@ class MonthlyStatusModel { } // 특정 날짜 집계 강제 업데이트 - static async updateDateSummary(date, workerId = null) { + static async updateDateSummary(date, userId = null) { const db = await getDb(); try { - if (workerId) { + if (userId) { // 특정 작업자만 업데이트 - await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, workerId]); + await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, userId]); } else { // 해당 날짜의 모든 작업자 업데이트 const [workers] = await db.execute(` - SELECT DISTINCT worker_id - FROM daily_work_reports + SELECT DISTINCT user_id + FROM daily_work_reports WHERE report_date = ? `, [date]); - for (const { worker_id } of workers) { - await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, worker_id]); + for (const { user_id } of workers) { + await db.execute('CALL UpdateMonthlyWorkerStatus(?, ?)', [date, user_id]); } } @@ -163,7 +163,7 @@ class MonthlyStatusModel { const [workerStatusCount] = await db.execute(` SELECT COUNT(*) as total_records, - COUNT(DISTINCT worker_id) as unique_workers, + COUNT(DISTINCT user_id) as unique_workers, COUNT(DISTINCT date) as unique_dates, MAX(last_updated) as last_update FROM monthly_worker_status diff --git a/system1-factory/api/models/pageAccessModel.js b/system1-factory/api/models/pageAccessModel.js index 0d50190..b1632ee 100644 --- a/system1-factory/api/models/pageAccessModel.js +++ b/system1-factory/api/models/pageAccessModel.js @@ -139,13 +139,13 @@ const PageAccessModel = { u.name, u.role_id, r.name as role_name, - u.worker_id, + u.user_id as worker_user_id, w.worker_name, w.job_type, COUNT(upa.page_id) as granted_pages_count FROM users u LEFT JOIN roles r ON u.role_id = r.id - LEFT JOIN workers w ON u.worker_id = w.worker_id + LEFT JOIN workers w ON u.user_id = w.user_id LEFT JOIN user_page_access upa ON u.user_id = upa.user_id AND upa.can_access = 1 WHERE u.is_active = 1 AND u.role_id IN (4, 5) diff --git a/system1-factory/api/models/tbmModel.js b/system1-factory/api/models/tbmModel.js index 27704a9..ad35da2 100644 --- a/system1-factory/api/models/tbmModel.js +++ b/system1-factory/api/models/tbmModel.js @@ -8,11 +8,11 @@ const TbmModel = { const db = await getDb(); const [result] = await db.query( `INSERT INTO tbm_sessions - (session_date, leader_id, project_id, work_type_id, task_id, work_location, created_by) + (session_date, leader_user_id, project_id, work_type_id, task_id, work_location, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ sessionData.session_date, - sessionData.leader_id, + sessionData.leader_user_id, sessionData.project_id || null, sessionData.work_type_id || null, sessionData.task_id || null, @@ -32,7 +32,7 @@ const TbmModel = { w.job_type as leader_job_type, u.username as created_by_username, u.name as created_by_name, - COUNT(DISTINCT ta.worker_id) as team_member_count, + COUNT(DISTINCT ta.user_id) as team_member_count, GROUP_CONCAT(DISTINCT w2.worker_name ORDER BY ta.assignment_id SEPARATOR ', ') as team_member_names, (SELECT COUNT(*) FROM tbm_transfers tf WHERE (tf.source_session_id = s.session_id OR tf.dest_session_id = s.session_id) @@ -46,10 +46,10 @@ const TbmModel = { first_t.task_name, first_wp.workplace_name as work_location FROM tbm_sessions s - LEFT JOIN workers w ON s.leader_id = w.worker_id + LEFT JOIN workers w ON s.leader_user_id = w.user_id LEFT JOIN sso_users u ON s.created_by = u.user_id LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id - LEFT JOIN workers w2 ON ta.worker_id = w2.worker_id + LEFT JOIN workers w2 ON ta.user_id = w2.user_id LEFT JOIN ( SELECT * FROM tbm_team_assignments WHERE (session_id, assignment_id) IN ( @@ -80,7 +80,7 @@ const TbmModel = { w.phone_number as leader_phone, u.username as created_by_username, u.name as created_by_name, - COUNT(DISTINCT ta.worker_id) as team_member_count, + COUNT(DISTINCT ta.user_id) as team_member_count, first_p.project_name, first_p.job_no, first_wt.name as work_type_name, @@ -90,7 +90,7 @@ const TbmModel = { first_wp.workplace_name as work_location, first_wc.category_name as workplace_category_name FROM tbm_sessions s - LEFT JOIN workers w ON s.leader_id = w.worker_id + LEFT JOIN workers w ON s.leader_user_id = w.user_id LEFT JOIN sso_users u ON s.created_by = u.user_id LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id LEFT JOIN ( @@ -165,8 +165,8 @@ const TbmModel = { await conn.query( `UPDATE tbm_team_assignments SET attendance_type = ?, attendance_hours = ? - WHERE session_id = ? AND worker_id = ?`, - [item.attendance_type, item.attendance_hours || null, sessionId, item.worker_id] + WHERE session_id = ? AND user_id = ?`, + [item.attendance_type, item.attendance_hours || null, sessionId, item.user_id] ); } @@ -174,8 +174,8 @@ const TbmModel = { const annualWorkers = attendanceData.filter(a => a.attendance_type === 'annual'); for (const aw of annualWorkers) { const [assignRows] = await conn.query( - 'SELECT assignment_id FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [sessionId, aw.worker_id] + 'SELECT assignment_id FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [sessionId, aw.user_id] ); if (assignRows.length > 0) { const [existingReport] = await conn.query( @@ -185,9 +185,9 @@ const TbmModel = { if (existingReport.length === 0) { await conn.query( `INSERT INTO daily_work_reports - (report_date, worker_id, project_id, work_hours, work_status_id, created_by, tbm_assignment_id, created_at) + (report_date, user_id, project_id, work_hours, work_status_id, created_by, tbm_assignment_id, created_at) VALUES (?, ?, 13, 8, 1, ?, ?, NOW())`, - [reportDate, aw.worker_id, createdBy, assignRows[0].assignment_id] + [reportDate, aw.user_id, createdBy, assignRows[0].assignment_id] ); } } @@ -207,7 +207,7 @@ const TbmModel = { for (const aw of annualWorkers) { try { const AttendanceModel = require('./attendanceModel'); - await AttendanceModel.syncWithWorkReports(aw.worker_id, reportDate); + await AttendanceModel.syncWithWorkReports(aw.user_id, reportDate); } catch (syncErr) { // 근태 동기화 오류 (무시됨) } @@ -237,7 +237,7 @@ const TbmModel = { const db = await getDb(); const [result] = await db.query( `INSERT INTO tbm_team_assignments - (session_id, worker_id, split_seq, assigned_role, work_detail, is_present, absence_reason, + (session_id, user_id, split_seq, assigned_role, work_detail, is_present, absence_reason, project_id, work_type_id, task_id, workplace_category_id, workplace_id, work_hours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE @@ -253,7 +253,7 @@ const TbmModel = { work_hours = COALESCE(VALUES(work_hours), work_hours)`, [ assignmentData.session_id, - assignmentData.worker_id, + assignmentData.user_id, assignmentData.split_seq || 0, assignmentData.assigned_role, assignmentData.work_detail, @@ -273,19 +273,19 @@ const TbmModel = { addSplitAssignment: async (assignmentData) => { const db = await getDb(); const [maxRows] = await db.query( - 'SELECT COALESCE(MAX(split_seq), -1) as max_seq FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [assignmentData.session_id, assignmentData.worker_id] + 'SELECT COALESCE(MAX(split_seq), -1) as max_seq FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [assignmentData.session_id, assignmentData.user_id] ); const nextSeq = (maxRows[0].max_seq || 0) + 1; const [result] = await db.query( `INSERT INTO tbm_team_assignments - (session_id, worker_id, split_seq, work_hours, project_id, work_type_id, + (session_id, user_id, split_seq, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`, [ assignmentData.session_id, - assignmentData.worker_id, + assignmentData.user_id, nextSeq, assignmentData.work_hours, assignmentData.project_id || null, @@ -306,7 +306,7 @@ const TbmModel = { const db = await getDb(); const values = members.map(m => [ sessionId, - m.worker_id, + m.user_id, m.assigned_role || null, m.work_detail || null, m.is_present !== undefined ? m.is_present : true, @@ -320,7 +320,7 @@ const TbmModel = { const [result] = await db.query( `INSERT INTO tbm_team_assignments - (session_id, worker_id, assigned_role, work_detail, is_present, absence_reason, + (session_id, user_id, assigned_role, work_detail, is_present, absence_reason, project_id, work_type_id, task_id, workplace_category_id, workplace_id) VALUES ?`, [values] @@ -343,7 +343,7 @@ const TbmModel = { wc.category_name AS workplace_category_name, wp.workplace_name FROM tbm_team_assignments ta - INNER JOIN workers w ON ta.worker_id = w.worker_id + INNER JOIN workers w ON ta.user_id = w.user_id LEFT JOIN projects p ON ta.project_id = p.project_id LEFT JOIN work_types wt ON ta.work_type_id = wt.id LEFT JOIN tasks t ON ta.task_id = t.task_id @@ -359,7 +359,7 @@ const TbmModel = { removeTeamMember: async (sessionId, workerId) => { const db = await getDb(); const [result] = await db.query( - `DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?`, + `DELETE FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?`, [sessionId, workerId] ); return result; @@ -464,13 +464,13 @@ const TbmModel = { const db = await getDb(); const [result] = await db.query( `INSERT INTO team_handovers - (session_id, from_leader_id, to_leader_id, handover_date, handover_time, + (session_id, from_leader_user_id, to_leader_user_id, handover_date, handover_time, reason, handover_notes, worker_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ handoverData.session_id, - handoverData.from_leader_id, - handoverData.to_leader_id, + handoverData.from_leader_user_id, + handoverData.to_leader_user_id, handoverData.handover_date, handoverData.handover_time, handoverData.reason, @@ -502,8 +502,8 @@ const TbmModel = { u.username as confirmed_by_username, u.name as confirmed_by_name FROM team_handovers h - INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id - INNER JOIN workers w2 ON h.to_leader_id = w2.worker_id + INNER JOIN workers w1 ON h.from_leader_user_id = w1.user_id + INNER JOIN workers w2 ON h.to_leader_user_id = w2.user_id LEFT JOIN sso_users u ON h.confirmed_by = u.user_id WHERE h.handover_date = ? ORDER BY h.handover_time DESC`, @@ -521,9 +521,9 @@ const TbmModel = { w1.phone_number as from_leader_phone, s.work_location FROM team_handovers h - INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id + INNER JOIN workers w1 ON h.from_leader_user_id = w1.user_id LEFT JOIN tbm_sessions s ON h.session_id = s.session_id - WHERE h.to_leader_id = ? AND h.is_confirmed = 0 + WHERE h.to_leader_user_id = ? AND h.is_confirmed = 0 ORDER BY h.handover_date DESC, h.handover_time DESC`, [toLeaderId] ); @@ -538,7 +538,7 @@ const TbmModel = { `SELECT DATE(session_date) as date, COUNT(DISTINCT session_id) as session_count, - COUNT(DISTINCT leader_id) as leader_count, + COUNT(DISTINCT leader_user_id) as leader_count, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count FROM tbm_sessions WHERE session_date BETWEEN ? AND ? @@ -553,16 +553,16 @@ const TbmModel = { const db = await getDb(); const [rows] = await db.query( `SELECT - s.leader_id, + s.leader_user_id, w.worker_name as leader_name, COUNT(DISTINCT s.session_id) as total_sessions, SUM(CASE WHEN s.status = 'completed' THEN 1 ELSE 0 END) as completed_sessions, - COUNT(DISTINCT ta.worker_id) as total_team_members + COUNT(DISTINCT ta.user_id) as total_team_members FROM tbm_sessions s - INNER JOIN workers w ON s.leader_id = w.worker_id + INNER JOIN workers w ON s.leader_user_id = w.user_id LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id WHERE s.session_date BETWEEN ? AND ? - GROUP BY s.leader_id + GROUP BY s.leader_user_id ORDER BY total_sessions DESC`, [startDate, endDate] ); @@ -597,7 +597,7 @@ const TbmModel = { `SELECT ta.assignment_id, ta.session_id, - ta.worker_id, + ta.user_id, ta.project_id, ta.work_type_id, ta.task_id, @@ -620,9 +620,9 @@ const TbmModel = { lw.worker_name as leader_name FROM tbm_team_assignments ta INNER JOIN tbm_sessions s ON ta.session_id = s.session_id - INNER JOIN workers w ON ta.worker_id = w.worker_id + INNER JOIN workers w ON ta.user_id = w.user_id LEFT JOIN sso_users creator ON s.created_by = creator.user_id - LEFT JOIN workers lw ON s.leader_id = lw.worker_id + LEFT JOIN workers lw ON s.leader_user_id = lw.user_id LEFT JOIN projects p ON ta.project_id = p.project_id LEFT JOIN work_types wt ON ta.work_type_id = wt.id LEFT JOIN tasks t ON ta.task_id = t.task_id diff --git a/system1-factory/api/models/tbmTransferModel.js b/system1-factory/api/models/tbmTransferModel.js index 7f60bf1..87953d2 100644 --- a/system1-factory/api/models/tbmTransferModel.js +++ b/system1-factory/api/models/tbmTransferModel.js @@ -13,15 +13,15 @@ const TbmTransferModel = { await conn.beginTransaction(); const { - transfer_type, worker_id, source_session_id, dest_session_id, + transfer_type, user_id, source_session_id, dest_session_id, hours, initiated_by, transfer_date, project_id, work_type_id, task_id, workplace_category_id, workplace_id } = transferData; // 1. source 세션에서 해당 작업자의 work_hours 업데이트 const [sourceRows] = await conn.query( - 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [source_session_id, worker_id] + 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [source_session_id, user_id] ); if (sourceRows.length === 0) { @@ -38,28 +38,28 @@ const TbmTransferModel = { } await conn.query( - 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', - [newSourceHours, source_session_id, worker_id] + 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?', + [newSourceHours, source_session_id, user_id] ); // 2. dest 세션에 작업자 INSERT (이미 있으면 work_hours 증가) const [destRows] = await conn.query( - 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [dest_session_id, worker_id] + 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [dest_session_id, user_id] ); if (destRows.length > 0) { const existingHours = destRows[0].work_hours === null ? 8 : parseFloat(destRows[0].work_hours); await conn.query( - 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', - [existingHours + parseFloat(hours), dest_session_id, worker_id] + 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?', + [existingHours + parseFloat(hours), dest_session_id, user_id] ); } else { await conn.query( `INSERT INTO tbm_team_assignments - (session_id, worker_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present) + (session_id, user_id, work_hours, project_id, work_type_id, task_id, workplace_category_id, workplace_id, is_present) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1)`, - [dest_session_id, worker_id, parseFloat(hours), + [dest_session_id, user_id, parseFloat(hours), project_id || null, work_type_id || null, task_id || null, workplace_category_id || null, workplace_id || null] ); @@ -68,18 +68,18 @@ const TbmTransferModel = { // 3. tbm_transfers에 로그 INSERT const [logResult] = await conn.query( `INSERT INTO tbm_transfers - (transfer_date, worker_id, source_session_id, dest_session_id, hours, transfer_type, initiated_by) + (transfer_date, user_id, source_session_id, dest_session_id, hours, transfer_type, initiated_by) VALUES (?, ?, ?, ?, ?, ?, ?)`, - [transfer_date, worker_id, source_session_id, dest_session_id, parseFloat(hours), transfer_type, initiated_by] + [transfer_date, user_id, source_session_id, dest_session_id, parseFloat(hours), transfer_type, initiated_by] ); // 4. 합계 시간 검증 (경고만, 차단 안함) const [totalRows] = await conn.query( `SELECT SUM(COALESCE(work_hours, 8)) as total_hours FROM tbm_team_assignments - WHERE worker_id = ? + WHERE user_id = ? AND session_id IN (SELECT session_id FROM tbm_sessions WHERE session_date = ?)`, - [worker_id, transfer_date] + [user_id, transfer_date] ); const totalHours = totalRows[0] ? parseFloat(totalRows[0].total_hours) : 0; @@ -128,8 +128,8 @@ const TbmTransferModel = { // 2. dest 세션에서 작업자 work_hours 감소 (또는 삭제) const [destRows] = await conn.query( - 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [t.dest_session_id, t.worker_id] + 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [t.dest_session_id, t.user_id] ); if (destRows.length > 0) { @@ -138,29 +138,29 @@ const TbmTransferModel = { if (newDestHours <= 0) { await conn.query( - 'DELETE FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [t.dest_session_id, t.worker_id] + 'DELETE FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [t.dest_session_id, t.user_id] ); } else { await conn.query( - 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', - [newDestHours, t.dest_session_id, t.worker_id] + 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?', + [newDestHours, t.dest_session_id, t.user_id] ); } } // 3. source 세션에서 작업자 work_hours 복원 const [sourceRows] = await conn.query( - 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND worker_id = ?', - [t.source_session_id, t.worker_id] + 'SELECT assignment_id, work_hours FROM tbm_team_assignments WHERE session_id = ? AND user_id = ?', + [t.source_session_id, t.user_id] ); if (sourceRows.length > 0) { const sourceHours = sourceRows[0].work_hours === null ? 8 : parseFloat(sourceRows[0].work_hours); const restoredHours = sourceHours + parseFloat(t.hours); await conn.query( - 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND worker_id = ?', - [restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.worker_id] + 'UPDATE tbm_team_assignments SET work_hours = ? WHERE session_id = ? AND user_id = ?', + [restoredHours >= 8 ? null : restoredHours, t.source_session_id, t.user_id] ); } @@ -191,11 +191,11 @@ const TbmTransferModel = { dl.worker_name as dest_leader_name, u.name as initiated_by_name FROM tbm_transfers t - INNER JOIN workers w ON t.worker_id = w.worker_id + INNER JOIN workers w ON t.user_id = w.user_id LEFT JOIN tbm_sessions ss ON t.source_session_id = ss.session_id - LEFT JOIN workers sl ON ss.leader_id = sl.worker_id + LEFT JOIN workers sl ON ss.leader_id = sl.user_id LEFT JOIN tbm_sessions ds ON t.dest_session_id = ds.session_id - LEFT JOIN workers dl ON ds.leader_id = dl.worker_id + LEFT JOIN workers dl ON ds.leader_id = dl.user_id LEFT JOIN sso_users u ON t.initiated_by = u.user_id WHERE t.transfer_date = ? ORDER BY t.created_at DESC @@ -213,7 +213,7 @@ const TbmTransferModel = { // 1. 해당 날짜의 모든 배정 가져오기 const [assignments] = await db.query(` SELECT - ta.worker_id, + ta.user_id, ta.session_id, ta.work_hours, w.worker_name, @@ -223,22 +223,22 @@ const TbmTransferModel = { s.status as session_status FROM tbm_team_assignments ta INNER JOIN tbm_sessions s ON ta.session_id = s.session_id - INNER JOIN workers w ON ta.worker_id = w.worker_id - LEFT JOIN workers lw ON s.leader_id = lw.worker_id + INNER JOIN workers w ON ta.user_id = w.user_id + LEFT JOIN workers lw ON s.leader_id = lw.user_id WHERE s.session_date = ? ORDER BY w.worker_name `, [date]); // 2. 모든 작업자 가져오기 (배정 안 된 사람도 포함) const [allWorkers] = await db.query( - "SELECT worker_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name" + "SELECT user_id, worker_name, job_type FROM workers WHERE status = 'active' AND department = '생산' ORDER BY worker_name" ); // 3. 작업자별 배정 현황 구성 const workerMap = {}; allWorkers.forEach(w => { - workerMap[w.worker_id] = { - worker_id: w.worker_id, + workerMap[w.user_id] = { + user_id: w.user_id, worker_name: w.worker_name, job_type: w.job_type, sessions: [], @@ -249,14 +249,14 @@ const TbmTransferModel = { assignments.forEach(a => { const hours = a.work_hours === null ? 8 : parseFloat(a.work_hours); - if (workerMap[a.worker_id]) { - workerMap[a.worker_id].sessions.push({ + if (workerMap[a.user_id]) { + workerMap[a.user_id].sessions.push({ session_id: a.session_id, leader_name: a.leader_name, work_hours: hours, session_status: a.session_status }); - workerMap[a.worker_id].total_hours += hours; + workerMap[a.user_id].total_hours += hours; } }); diff --git a/system1-factory/api/models/userModel.js b/system1-factory/api/models/userModel.js index bd4c54f..b59858a 100644 --- a/system1-factory/api/models/userModel.js +++ b/system1-factory/api/models/userModel.js @@ -7,7 +7,7 @@ const findByUsername = async (username) => { const [rows] = await db.query( `SELECT u.user_id, u.username, u.password, u.name, u.email, u.role_id, r.name as role_name, - u._access_level_old as access_level, u.worker_id, u.is_active, + u._access_level_old as access_level, u.is_active, u.last_login_at, u.password_changed_at, u.failed_login_attempts, u.locked_until, u.created_at, u.updated_at FROM users u diff --git a/system1-factory/api/models/vacationBalanceModel.js b/system1-factory/api/models/vacationBalanceModel.js index 62096d4..87b9c21 100644 --- a/system1-factory/api/models/vacationBalanceModel.js +++ b/system1-factory/api/models/vacationBalanceModel.js @@ -9,7 +9,7 @@ const vacationBalanceModel = { /** * 특정 작업자의 모든 휴가 잔액 조회 (특정 연도) */ - async getByWorkerAndYear(workerId, year) { + async getByWorkerAndYear(userId, year) { const db = await getDb(); const [rows] = await db.query(` SELECT @@ -20,16 +20,16 @@ const vacationBalanceModel = { vt.is_special FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? AND vbd.year = ? + WHERE vbd.user_id = ? AND vbd.year = ? ORDER BY vt.priority ASC, vt.type_name ASC - `, [workerId, year]); + `, [userId, year]); return rows; }, /** * 특정 작업자의 특정 휴가 유형 잔액 조회 */ - async getByWorkerTypeYear(workerId, vacationTypeId, year) { + async getByWorkerTypeYear(userId, vacationTypeId, year) { const db = await getDb(); const [rows] = await db.query(` SELECT @@ -38,10 +38,10 @@ const vacationBalanceModel = { vt.type_code FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? + WHERE vbd.user_id = ? AND vbd.vacation_type_id = ? AND vbd.year = ? - `, [workerId, vacationTypeId, year]); + `, [userId, vacationTypeId, year]); return rows; }, @@ -59,7 +59,7 @@ const vacationBalanceModel = { vt.type_code, vt.priority FROM vacation_balance_details vbd - INNER JOIN workers w ON vbd.worker_id = w.worker_id + INNER JOIN workers w ON vbd.user_id = w.user_id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.year = ? AND w.employment_status = 'employed' @@ -98,39 +98,39 @@ const vacationBalanceModel = { /** * 작업자의 휴가 사용 일수 업데이트 (차감) */ - async deductDays(workerId, vacationTypeId, year, daysToDeduct) { + async deductDays(userId, vacationTypeId, year, daysToDeduct) { const db = await getDb(); const [result] = await db.query(` UPDATE vacation_balance_details SET used_days = used_days + ?, updated_at = NOW() - WHERE worker_id = ? + WHERE user_id = ? AND vacation_type_id = ? AND year = ? - `, [daysToDeduct, workerId, vacationTypeId, year]); + `, [daysToDeduct, userId, vacationTypeId, year]); return result; }, /** * 작업자의 휴가 사용 일수 복구 (취소) */ - async restoreDays(workerId, vacationTypeId, year, daysToRestore) { + async restoreDays(userId, vacationTypeId, year, daysToRestore) { const db = await getDb(); const [result] = await db.query(` UPDATE vacation_balance_details SET used_days = GREATEST(0, used_days - ?), updated_at = NOW() - WHERE worker_id = ? + WHERE user_id = ? AND vacation_type_id = ? AND year = ? - `, [daysToRestore, workerId, vacationTypeId, year]); + `, [daysToRestore, userId, vacationTypeId, year]); return result; }, /** * 특정 작업자의 사용 가능한 휴가 일수 확인 */ - async getAvailableVacationDays(workerId, year) { + async getAvailableVacationDays(userId, year) { const db = await getDb(); const [rows] = await db.query(` SELECT @@ -144,11 +144,11 @@ const vacationBalanceModel = { vbd.remaining_days FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? + WHERE vbd.user_id = ? AND vbd.year = ? AND vbd.remaining_days > 0 ORDER BY vt.priority ASC - `, [workerId, year]); + `, [userId, year]); return rows; }, @@ -162,11 +162,11 @@ const vacationBalanceModel = { const db = await getDb(); const query = `INSERT INTO vacation_balance_details - (worker_id, vacation_type_id, year, total_days, used_days, notes, created_by) + (user_id, vacation_type_id, year, total_days, used_days, notes, created_by) VALUES ?`; const values = balances.map(b => [ - b.worker_id, + b.user_id, b.vacation_type_id, b.year, b.total_days || 0, @@ -202,7 +202,7 @@ const vacationBalanceModel = { /** * 휴가 사용 시 우선순위에 따라 잔액에서 차감 */ - async deductByPriority(workerId, year, daysToDeduct) { + async deductByPriority(userId, year, daysToDeduct) { const db = await getDb(); const [balances] = await db.query(` @@ -211,13 +211,13 @@ const vacationBalanceModel = { vt.type_code, vt.type_name, vt.priority FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? AND vbd.year = ? + WHERE vbd.user_id = ? AND vbd.year = ? AND (vbd.total_days - vbd.used_days) > 0 ORDER BY vt.priority ASC - `, [workerId, year]); + `, [userId, year]); if (balances.length === 0) { - console.warn(`[VacationBalance] 작업자 ${workerId}의 ${year}년 휴가 잔액이 없습니다`); + console.warn(`[VacationBalance] 작업자 ${userId}의 ${year}년 휴가 잔액이 없습니다`); return { success: false, message: '휴가 잔액이 없습니다', deducted: 0 }; } @@ -248,14 +248,14 @@ const vacationBalanceModel = { } } - console.log(`[VacationBalance] 작업자 ${workerId}: ${daysToDeduct}일 차감 완료`, deductions); + console.log(`[VacationBalance] 작업자 ${userId}: ${daysToDeduct}일 차감 완료`, deductions); return { success: true, deductions, totalDeducted: daysToDeduct - remaining }; }, /** * 휴가 취소 시 우선순위 역순으로 복구 */ - async restoreByPriority(workerId, year, daysToRestore) { + async restoreByPriority(userId, year, daysToRestore) { const db = await getDb(); const [balances] = await db.query(` @@ -263,10 +263,10 @@ const vacationBalanceModel = { vt.type_code, vt.type_name, vt.priority FROM vacation_balance_details vbd INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id - WHERE vbd.worker_id = ? AND vbd.year = ? + WHERE vbd.user_id = ? AND vbd.year = ? AND vbd.used_days > 0 ORDER BY vt.priority DESC - `, [workerId, year]); + `, [userId, year]); let remaining = daysToRestore; const restorations = []; @@ -295,7 +295,7 @@ const vacationBalanceModel = { } } - console.log(`[VacationBalance] 작업자 ${workerId}: ${daysToRestore}일 복구 완료`, restorations); + console.log(`[VacationBalance] 작업자 ${userId}: ${daysToRestore}일 복구 완료`, restorations); return { success: true, restorations, totalRestored: daysToRestore - remaining }; }, @@ -311,7 +311,7 @@ const vacationBalanceModel = { vt.type_name, vt.type_code FROM vacation_balance_details vbd - INNER JOIN workers w ON vbd.worker_id = w.worker_id + INNER JOIN workers w ON vbd.user_id = w.user_id INNER JOIN vacation_types vt ON vbd.vacation_type_id = vt.id WHERE vbd.id = ? `, [id]); diff --git a/system1-factory/api/models/vacationRequestModel.js b/system1-factory/api/models/vacationRequestModel.js index 75cae22..0e032c4 100644 --- a/system1-factory/api/models/vacationRequestModel.js +++ b/system1-factory/api/models/vacationRequestModel.js @@ -30,7 +30,7 @@ const vacationRequestModel = { requester.name as requester_name, reviewer.name as reviewer_name FROM vacation_requests vr - INNER JOIN workers w ON vr.worker_id = w.worker_id + INNER JOIN workers w ON vr.user_id = w.user_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id LEFT JOIN users requester ON vr.requested_by = requester.user_id LEFT JOIN users reviewer ON vr.reviewed_by = reviewer.user_id @@ -39,9 +39,9 @@ const vacationRequestModel = { const params = []; - if (filters.worker_id) { - query += ` AND vr.worker_id = ?`; - params.push(filters.worker_id); + if (filters.user_id) { + query += ` AND vr.user_id = ?`; + params.push(filters.user_id); } if (filters.status) { query += ` AND vr.status = ?`; @@ -85,7 +85,7 @@ const vacationRequestModel = { reviewer.name as reviewer_name, reviewer.username as reviewer_username FROM vacation_requests vr - INNER JOIN workers w ON vr.worker_id = w.worker_id + INNER JOIN workers w ON vr.user_id = w.user_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id LEFT JOIN users requester ON vr.requested_by = requester.user_id LEFT JOIN users reviewer ON vr.reviewed_by = reviewer.user_id @@ -128,35 +128,35 @@ const vacationRequestModel = { /** * 특정 작업자의 대기 중인 휴가 신청 수 */ - async getPendingCount(workerId) { + async getPendingCount(userId) { const db = await getDb(); const [rows] = await db.query(` SELECT COUNT(*) as count FROM vacation_requests - WHERE worker_id = ? AND status = 'pending' - `, [workerId]); + WHERE user_id = ? AND status = 'pending' + `, [userId]); return rows; }, /** * 특정 작업자의 승인된 휴가 일수 합계 (특정 기간) */ - async getApprovedDaysInPeriod(workerId, startDate, endDate) { + async getApprovedDaysInPeriod(userId, startDate, endDate) { const db = await getDb(); const [rows] = await db.query(` SELECT COALESCE(SUM(days_used), 0) as total_days FROM vacation_requests - WHERE worker_id = ? AND status = 'approved' AND start_date >= ? AND end_date <= ? - `, [workerId, startDate, endDate]); + WHERE user_id = ? AND status = 'approved' AND start_date >= ? AND end_date <= ? + `, [userId, startDate, endDate]); return rows; }, /** * 휴가 기간 중복 체크 */ - async checkOverlap(workerId, startDate, endDate, excludeRequestId = null) { + async checkOverlap(userId, startDate, endDate, excludeRequestId = null) { const db = await getDb(); let query = ` SELECT COUNT(*) as count FROM vacation_requests - WHERE worker_id = ? + WHERE user_id = ? AND status IN ('pending', 'approved') AND ( (start_date <= ? AND end_date >= ?) OR @@ -164,7 +164,7 @@ const vacationRequestModel = { (start_date >= ? AND end_date <= ?) ) `; - const params = [workerId, startDate, startDate, endDate, endDate, startDate, endDate]; + const params = [userId, startDate, startDate, endDate, endDate, startDate, endDate]; if (excludeRequestId) { query += ` AND request_id != ?`; @@ -187,7 +187,7 @@ const vacationRequestModel = { vt.type_name as vacation_type_name, requester.name as requester_name FROM vacation_requests vr - INNER JOIN workers w ON vr.worker_id = w.worker_id + INNER JOIN workers w ON vr.user_id = w.user_id INNER JOIN vacation_types vt ON vr.vacation_type_id = vt.id LEFT JOIN users requester ON vr.requested_by = requester.user_id WHERE vr.status = 'pending' diff --git a/system1-factory/api/models/workReportModel.js b/system1-factory/api/models/workReportModel.js index 3766369..5ca352b 100644 --- a/system1-factory/api/models/workReportModel.js +++ b/system1-factory/api/models/workReportModel.js @@ -12,14 +12,14 @@ const createBatch = async (reports) => { const sql = ` INSERT INTO WorkReports - (\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo) + (\`date\`, user_id, project_id, task_id, overtime_hours, work_details, memo) VALUES (?, ?, ?, ?, ?, ?, ?) `; for (const rpt of reports) { const params = [ rpt.date, - rpt.worker_id, + rpt.user_id, rpt.project_id, rpt.task_id || null, rpt.overtime_hours || null, @@ -44,18 +44,18 @@ const createBatch = async (reports) => { const create = async (report) => { const db = await getDb(); const { - date, worker_id, project_id, + date, user_id, project_id, task_id, overtime_hours, work_details, memo } = report; const [result] = await db.query( `INSERT INTO WorkReports - (\`date\`, worker_id, project_id, task_id, overtime_hours, work_details, memo) + (\`date\`, user_id, project_id, task_id, overtime_hours, work_details, memo) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ date, - worker_id, + user_id, project_id, task_id || null, overtime_hours || null, @@ -74,7 +74,7 @@ const getAllByDate = async (date) => { const db = await getDb(); const sql = ` SELECT - wr.worker_id, + wr.user_id, wr.id, wr.\`date\`, w.worker_name, @@ -84,7 +84,7 @@ const getAllByDate = async (date) => { wr.work_details, wr.memo FROM WorkReports wr - LEFT JOIN workers w ON wr.worker_id = w.worker_id + LEFT JOIN workers w ON wr.user_id = w.user_id LEFT JOIN projects p ON wr.project_id = p.project_id LEFT JOIN Tasks t ON wr.task_id = t.task_id WHERE wr.\`date\` = ? @@ -100,7 +100,7 @@ const getAllByDate = async (date) => { const getByRange = async (start, end) => { const db = await getDb(); const [rows] = await db.query( - `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports + `SELECT id, \`date\`, user_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE \`date\` BETWEEN ? AND ? ORDER BY \`date\` ASC`, [start, end] @@ -114,7 +114,7 @@ const getByRange = async (start, end) => { const getById = async (id) => { const db = await getDb(); const [rows] = await db.query( - `SELECT id, \`date\`, worker_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`, + `SELECT id, \`date\`, user_id, project_id, morning_task_id, afternoon_task_id, overtime_hours, overtime_task_id, work_details, note, memo, created_at, updated_at, morning_project_id, afternoon_project_id, overtime_project_id, task_id FROM WorkReports WHERE id = ?`, [id] ); return rows[0]; @@ -126,7 +126,7 @@ const getById = async (id) => { const update = async (id, report) => { const db = await getDb(); const { - date, worker_id, project_id, + date, user_id, project_id, task_id, overtime_hours, work_details, memo } = report; @@ -134,7 +134,7 @@ const update = async (id, report) => { const [result] = await db.query( `UPDATE WorkReports SET \`date\` = ?, - worker_id = ?, + user_id = ?, project_id = ?, task_id = ?, overtime_hours = ?, @@ -144,7 +144,7 @@ const update = async (id, report) => { WHERE id = ?`, [ date, - worker_id, + user_id, project_id, task_id || null, overtime_hours || null, @@ -172,11 +172,11 @@ const remove = async (id) => { /** * 8. 중복 확인 */ -const existsByDateAndWorker = async (date, worker_id) => { +const existsByDateAndWorker = async (date, user_id) => { const db = await getDb(); const [rows] = await db.query( - `SELECT 1 FROM WorkReports WHERE \`date\` = ? AND worker_id = ? LIMIT 1`, - [date, worker_id] + `SELECT 1 FROM WorkReports WHERE \`date\` = ? AND user_id = ? LIMIT 1`, + [date, user_id] ); return rows.length > 0; }; diff --git a/system1-factory/api/models/workerModel.js b/system1-factory/api/models/workerModel.js index b549750..bb1315b 100644 --- a/system1-factory/api/models/workerModel.js +++ b/system1-factory/api/models/workerModel.js @@ -39,39 +39,60 @@ const getAll = async () => { const [rows] = await db.query(` SELECT w.*, + w.user_id, CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active, - u.user_id, + su.username, d.department_name FROM workers w - LEFT JOIN users u ON w.worker_id = u.worker_id + LEFT JOIN sso_users su ON w.user_id = su.user_id LEFT JOIN departments d ON w.department_id = d.department_id - ORDER BY w.worker_id DESC + ORDER BY w.worker_name ASC `); return rows; }; -// 3. 단일 조회 +// 3. 단일 조회 (worker_id 기준 - 하위 호환성 유지) const getById = async (worker_id) => { const db = await getDb(); const [rows] = await db.query(` SELECT w.*, + w.user_id, CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active, - u.user_id, + su.username, d.department_name FROM workers w - LEFT JOIN users u ON w.worker_id = u.worker_id + LEFT JOIN sso_users su ON w.user_id = su.user_id LEFT JOIN departments d ON w.department_id = d.department_id WHERE w.worker_id = ? `, [worker_id]); return rows[0]; }; -// 4. 작업자 수정 +// 3-1. 단일 조회 (user_id 기준) +const getByUserId = async (userId) => { + const db = await getDb(); + const [rows] = await db.query(` + SELECT + w.*, + w.user_id, + CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active, + su.username, + d.department_name + FROM workers w + LEFT JOIN sso_users su ON w.user_id = su.user_id + LEFT JOIN departments d ON w.department_id = d.department_id + WHERE w.user_id = ? + `, [userId]); + return rows[0]; +}; + +// 4. 작업자 수정 (worker_id 또는 user_id 기준) const update = async (worker) => { const db = await getDb(); const { worker_id, + user_id, worker_name, job_type, status, @@ -123,12 +144,22 @@ const update = async (worker) => { throw new Error('업데이트할 필드가 없습니다'); } - values.push(worker_id); // WHERE 조건용 + // WHERE 조건: user_id 우선, 없으면 worker_id 사용 + let whereClause; + if (user_id !== undefined) { + whereClause = 'user_id = ?'; + values.push(user_id); + } else if (worker_id !== undefined) { + whereClause = 'worker_id = ?'; + values.push(worker_id); + } else { + throw new Error('worker_id 또는 user_id가 필요합니다'); + } - const query = `UPDATE workers SET ${updates.join(', ')} WHERE worker_id = ?`; + const query = `UPDATE workers SET ${updates.join(', ')} WHERE ${whereClause}`; - console.log('🔍 실행할 SQL:', query); - console.log('🔍 SQL 파라미터:', values); + console.log('실행할 SQL:', query); + console.log('SQL 파라미터:', values); const [result] = await db.query(query, values); @@ -192,6 +223,7 @@ module.exports = { create, getAll, getById, + getByUserId, update, remove }; diff --git a/system1-factory/api/public/pages/work_analysis_dashboard.html b/system1-factory/api/public/pages/work_analysis_dashboard.html index 6aa8cd3..79506e0 100644 --- a/system1-factory/api/public/pages/work_analysis_dashboard.html +++ b/system1-factory/api/public/pages/work_analysis_dashboard.html @@ -539,7 +539,7 @@ charts.workerStats = new Chart(ctx, { type: 'bar', data: { - labels: data.map(item => `작업자 ${item.worker_id}`), + labels: data.map(item => `작업자 ${item.user_id}`), datasets: [{ label: '총 작업시간', data: data.map(item => item.totalHours), @@ -666,7 +666,7 @@ ${data.map(item => ` ${item.report_date} - 작업자 ${item.worker_id} + 작업자 ${item.user_id} 프로젝트 ${item.project_id} 유형 ${item.work_type_id} ${item.work_hours}시간 diff --git a/system1-factory/api/routes/attendanceRoutes.js b/system1-factory/api/routes/attendanceRoutes.js index 99e4c6b..d65f4c5 100644 --- a/system1-factory/api/routes/attendanceRoutes.js +++ b/system1-factory/api/routes/attendanceRoutes.js @@ -32,7 +32,7 @@ router.get('/attendance-types', AttendanceController.getAttendanceTypes); router.get('/vacation-types', AttendanceController.getVacationTypes); // 작업자 휴가 잔여 조회 -router.get('/vacation-balance/:worker_id', AttendanceController.getWorkerVacationBalance); +router.get('/vacation-balance/:user_id', AttendanceController.getWorkerVacationBalance); // 월별 근태 통계 router.get('/monthly-stats', AttendanceController.getMonthlyAttendanceStats); diff --git a/system1-factory/api/routes/dailyWorkReportRoutes.js b/system1-factory/api/routes/dailyWorkReportRoutes.js index f805c57..0dc64bc 100644 --- a/system1-factory/api/routes/dailyWorkReportRoutes.js +++ b/system1-factory/api/routes/dailyWorkReportRoutes.js @@ -26,22 +26,22 @@ router.put('/error-types/:id', dailyWorkReportController.updateErrorType); router.delete('/error-types/:id', dailyWorkReportController.deleteErrorType); // 🔄 누적 관련 새로운 라우트들 (누적입력 시스템 전용) -router.get('/accumulated', dailyWorkReportController.getAccumulatedReports); // ?date=2024-06-16&worker_id=1 -router.get('/contributors', dailyWorkReportController.getContributorsSummary); // ?date=2024-06-16&worker_id=1 -router.get('/my-data', dailyWorkReportController.getMyAccumulatedData); // ?date=2024-06-16&worker_id=1 +router.get('/accumulated', dailyWorkReportController.getAccumulatedReports); // ?date=2024-06-16&user_id=1 +router.get('/contributors', dailyWorkReportController.getContributorsSummary); // ?date=2024-06-16&user_id=1 +router.get('/my-data', dailyWorkReportController.getMyAccumulatedData); // ?date=2024-06-16&user_id=1 // ✅ check-overwrite 엔드포인트 추가 (누락된 엔드포인트) router.get('/check-overwrite', (req, res) => { - const { date, worker_id } = req.query; - - if (!date || !worker_id) { - return res.status(400).json({ - error: 'date와 worker_id가 필요합니다.', - example: 'date=2025-06-16&worker_id=1' + const { date, user_id } = req.query; + + if (!date || !user_id) { + return res.status(400).json({ + error: 'date와 user_id가 필요합니다.', + example: 'date=2025-06-16&user_id=1' }); } - console.log(`🔍 덮어쓰기 권한 확인: 날짜=${date}, 작업자=${worker_id} (누적입력모드)`); + console.log(`🔍 덮어쓰기 권한 확인: 날짜=${date}, 작업자=${user_id} (누적입력모드)`); // 누적입력 시스템에서는 항상 덮어쓰기 가능 (실제로는 누적만 함) res.json({ @@ -49,7 +49,7 @@ router.get('/check-overwrite', (req, res) => { reason: 'accumulate_mode', message: '누적입력 모드에서는 항상 추가 가능합니다.', date, - worker_id, + user_id, timestamp: new Date().toISOString() }); }); @@ -84,7 +84,7 @@ router.get('/', dailyWorkReportController.getDailyWorkReports); router.put('/:id', dailyWorkReportController.updateWorkReport); // 🗑️ 작업자의 특정 날짜 전체 삭제 -router.delete('/date/:date/worker/:worker_id', dailyWorkReportController.removeDailyWorkReportByDateAndWorker); +router.delete('/date/:date/worker/:user_id', dailyWorkReportController.removeDailyWorkReportByDateAndWorker); // 🗑️ 특정 작업보고서 삭제 (항상 가장 마지막에 정의) router.delete('/:id', dailyWorkReportController.removeDailyWorkReport); diff --git a/system1-factory/api/routes/systemRoutes.js b/system1-factory/api/routes/systemRoutes.js index ff5cd22..6e8306d 100644 --- a/system1-factory/api/routes/systemRoutes.js +++ b/system1-factory/api/routes/systemRoutes.js @@ -231,7 +231,7 @@ router.post('/migrations/fix-work-type-id', async (req, res) => { dwr.report_date FROM daily_work_reports dwr INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id - INNER JOIN workers w ON dwr.worker_id = w.worker_id + INNER JOIN workers w ON dwr.user_id = w.user_id WHERE dwr.tbm_assignment_id IS NOT NULL AND ta.task_id IS NOT NULL AND dwr.work_type_id != ta.task_id @@ -271,7 +271,7 @@ router.post('/migrations/fix-work-type-id', async (req, res) => { INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id LEFT JOIN tasks t ON dwr.work_type_id = t.task_id LEFT JOIN work_types wt ON t.work_type_id = wt.id - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id WHERE dwr.tbm_assignment_id IS NOT NULL ORDER BY dwr.report_date DESC LIMIT 10 diff --git a/system1-factory/api/routes/tbmRoutes.js b/system1-factory/api/routes/tbmRoutes.js index 2b21464..03fd87c 100644 --- a/system1-factory/api/routes/tbmRoutes.js +++ b/system1-factory/api/routes/tbmRoutes.js @@ -48,7 +48,7 @@ router.get('/sessions/:sessionId/team', requireAuth, TbmController.getTeamMember router.delete('/sessions/:sessionId/team/clear', requireAuth, TbmController.clearAllTeamMembers); // 팀원 제거 -router.delete('/sessions/:sessionId/team/:workerId', requireAuth, TbmController.removeTeamMember); +router.delete('/sessions/:sessionId/team/:userId', requireAuth, TbmController.removeTeamMember); // ==================== 안전 체크리스트 관련 ==================== diff --git a/system1-factory/api/routes/userRoutes.js b/system1-factory/api/routes/userRoutes.js index 4e6a93a..7e296be 100644 --- a/system1-factory/api/routes/userRoutes.js +++ b/system1-factory/api/routes/userRoutes.js @@ -59,7 +59,7 @@ router.get('/me/attendance-records', async (req, res) => { const AttendanceModel = require('../models/attendanceModel'); const startDate = `${year}-${String(month).padStart(2, '0')}-01`; const endDate = `${year}-${String(month).padStart(2, '0')}-31`; - const records = await AttendanceModel.getDailyRecords(startDate, endDate, req.user.worker_id); + const records = await AttendanceModel.getDailyRecords(startDate, endDate, req.user.user_id); res.json({ success: true, data: records }); } catch (error) { res.status(500).json({ success: false, error: error.message }); @@ -71,7 +71,7 @@ router.get('/me/vacation-balance', async (req, res) => { try { const AttendanceModel = require('../models/attendanceModel'); const year = req.query.year || new Date().getFullYear(); - const balance = await AttendanceModel.getWorkerVacationBalance(req.user.worker_id, year); + const balance = await AttendanceModel.getWorkerVacationBalance(req.user.user_id, year); res.json({ success: true, data: balance }); } catch (error) { res.status(500).json({ success: false, error: error.message }); @@ -84,8 +84,8 @@ router.get('/me/work-reports', async (req, res) => { const { startDate, endDate } = req.query; const db = require('../config/database'); const reports = await db.query( - 'SELECT * FROM daily_work_reports WHERE worker_id = ? AND report_date BETWEEN ? AND ? ORDER BY report_date DESC', - [req.user.worker_id, startDate, endDate] + 'SELECT * FROM daily_work_reports WHERE user_id = ? AND report_date BETWEEN ? AND ? ORDER BY report_date DESC', + [req.user.user_id, startDate, endDate] ); res.json({ success: true, data: reports }); } catch (error) { @@ -104,8 +104,8 @@ router.get('/me/monthly-stats', async (req, res) => { SUM(total_work_hours) as month_hours, COUNT(DISTINCT record_date) as work_days FROM daily_attendance_records - WHERE worker_id = ? AND YEAR(record_date) = ? AND MONTH(record_date) = ?`, - [req.user.worker_id, year, month] + WHERE user_id = ? AND YEAR(record_date) = ? AND MONTH(record_date) = ?`, + [req.user.user_id, year, month] ); res.json({ success: true, data: stats[0] || { month_hours: 0, work_days: 0 } }); } catch (error) { diff --git a/system1-factory/api/routes/vacationBalanceRoutes.js b/system1-factory/api/routes/vacationBalanceRoutes.js index e44872f..f0f5f58 100644 --- a/system1-factory/api/routes/vacationBalanceRoutes.js +++ b/system1-factory/api/routes/vacationBalanceRoutes.js @@ -11,10 +11,10 @@ const vacationBalanceController = require('../controllers/vacationBalanceControl router.get('/year/:year', vacationBalanceController.getAllByYear); // 특정 작업자의 휴가 잔액 조회 (특정 연도) -router.get('/worker/:workerId/year/:year', vacationBalanceController.getByWorkerAndYear); +router.get('/worker/:userId/year/:year', vacationBalanceController.getByWorkerAndYear); // 작업자의 사용 가능한 휴가 일수 조회 -router.get('/worker/:workerId/year/:year/available', vacationBalanceController.getAvailableDays); +router.get('/worker/:userId/year/:year/available', vacationBalanceController.getAvailableDays); // 근속년수 기반 연차 자동 계산 및 생성 (관리자만) router.post('/auto-calculate', vacationBalanceController.autoCalculateAndCreate); diff --git a/system1-factory/api/routes/workerRoutes.js b/system1-factory/api/routes/workerRoutes.js index 15d5f0e..5677f4f 100644 --- a/system1-factory/api/routes/workerRoutes.js +++ b/system1-factory/api/routes/workerRoutes.js @@ -96,7 +96,7 @@ router.get('/', workerController.getAllWorkers); /** * @swagger - * /api/workers/{worker_id}: + * /api/workers/{user_id}: * get: * tags: [Workers] * summary: 특정 작업자 조회 @@ -105,7 +105,7 @@ router.get('/', workerController.getAllWorkers); * - bearerAuth: [] * parameters: * - in: path - * name: worker_id + * name: user_id * required: true * schema: * type: integer @@ -142,7 +142,7 @@ router.get('/', workerController.getAllWorkers); * - bearerAuth: [] * parameters: * - in: path - * name: worker_id + * name: user_id * required: true * schema: * type: integer @@ -193,7 +193,7 @@ router.get('/', workerController.getAllWorkers); * - bearerAuth: [] * parameters: * - in: path - * name: worker_id + * name: user_id * required: true * schema: * type: integer @@ -221,8 +221,8 @@ router.get('/', workerController.getAllWorkers); * 500: * description: 서버 오류 */ -router.get('/:worker_id', workerController.getWorkerById); -router.put('/:worker_id', workerController.updateWorker); -router.delete('/:worker_id', workerController.removeWorker); +router.get('/:user_id', workerController.getWorkerById); +router.put('/:user_id', workerController.updateWorker); +router.delete('/:user_id', workerController.removeWorker); module.exports = router; \ No newline at end of file diff --git a/system1-factory/api/services/attendanceService.js b/system1-factory/api/services/attendanceService.js index 6d6a182..c207724 100644 --- a/system1-factory/api/services/attendanceService.js +++ b/system1-factory/api/services/attendanceService.js @@ -53,7 +53,7 @@ const getDailyAttendanceStatusService = async (date) => { /** * 일일 근태 기록 조회 */ -const getDailyAttendanceRecordsService = async (date, workerId = null) => { +const getDailyAttendanceRecordsService = async (date, userId = null) => { if (!date) { throw new ValidationError('날짜가 필요합니다', { required: ['date'], @@ -61,10 +61,10 @@ const getDailyAttendanceRecordsService = async (date, workerId = null) => { }); } - logger.info('일일 근태 기록 조회 요청', { date, workerId }); + logger.info('일일 근태 기록 조회 요청', { date, userId }); try { - const records = await AttendanceModel.getDailyAttendanceRecords(date, workerId); + const records = await AttendanceModel.getDailyAttendanceRecords(date, userId); logger.info('일일 근태 기록 조회 성공', { date, count: records.length }); return records; } catch (error) { @@ -76,7 +76,7 @@ const getDailyAttendanceRecordsService = async (date, workerId = null) => { /** * 기간별 근태 기록 조회 (월별 조회용) */ -const getAttendanceRecordsByRangeService = async (startDate, endDate, workerId = null) => { +const getAttendanceRecordsByRangeService = async (startDate, endDate, userId = null) => { if (!startDate || !endDate) { throw new ValidationError('시작 날짜와 종료 날짜가 필요합니다', { required: ['start_date', 'end_date'], @@ -84,10 +84,10 @@ const getAttendanceRecordsByRangeService = async (startDate, endDate, workerId = }); } - logger.info('기간별 근태 기록 조회 요청', { startDate, endDate, workerId }); + logger.info('기간별 근태 기록 조회 요청', { startDate, endDate, userId }); try { - const records = await AttendanceModel.getDailyRecords(startDate, endDate, workerId); + const records = await AttendanceModel.getDailyRecords(startDate, endDate, userId); logger.info('기간별 근태 기록 조회 성공', { startDate, endDate, count: records.length }); return records; } catch (error) { @@ -103,7 +103,7 @@ const getAttendanceRecordsByRangeService = async (startDate, endDate, workerId = const upsertAttendanceRecordService = async (recordData) => { const { record_date, - worker_id, + user_id, total_work_hours, attendance_type_id, vacation_type_id, @@ -115,25 +115,25 @@ const upsertAttendanceRecordService = async (recordData) => { } = recordData; // 필수 필드 검증 - if (!record_date || !worker_id) { + if (!record_date || !user_id) { throw new ValidationError('필수 필드가 누락되었습니다', { - required: ['record_date', 'worker_id'], - received: { record_date, worker_id } + required: ['record_date', 'user_id'], + received: { record_date, user_id } }); } - logger.info('근태 기록 저장 요청', { record_date, worker_id, vacation_type_id }); + logger.info('근태 기록 저장 요청', { record_date, user_id, vacation_type_id }); try { // 1. 기존 기록 조회 (휴가 연동을 위해) - const existingRecords = await AttendanceModel.getDailyAttendanceRecords(record_date, worker_id); - const existingRecord = existingRecords.find(r => r.worker_id === worker_id); + const existingRecords = await AttendanceModel.getDailyAttendanceRecords(record_date, user_id); + const existingRecord = existingRecords.find(r => r.user_id === user_id); const previousVacationTypeId = existingRecord?.vacation_type_id || null; // 2. 근태 기록 저장 const result = await AttendanceModel.upsertAttendanceRecord({ record_date, - worker_id, + user_id, total_work_hours, work_attendance_type_id: attendance_type_id, vacation_type_id, @@ -150,21 +150,21 @@ const upsertAttendanceRecordService = async (recordData) => { if (previousDays !== newDays) { // 이전 휴가 복구 if (previousDays > 0) { - await vacationBalanceModel.restoreByPriority(worker_id, year, previousDays); - logger.info('휴가 잔액 복구', { worker_id, year, restored: previousDays }); + await vacationBalanceModel.restoreByPriority(user_id, year, previousDays); + logger.info('휴가 잔액 복구', { user_id, year, restored: previousDays }); } // 새 휴가 차감 if (newDays > 0) { - await vacationBalanceModel.deductByPriority(worker_id, year, newDays); - logger.info('휴가 잔액 차감', { worker_id, year, deducted: newDays }); + await vacationBalanceModel.deductByPriority(user_id, year, newDays); + logger.info('휴가 잔액 차감', { user_id, year, deducted: newDays }); } } - logger.info('근태 기록 저장 성공', { record_date, worker_id }); + logger.info('근태 기록 저장 성공', { record_date, user_id }); return result; } catch (error) { - logger.error('근태 기록 저장 실패', { record_date, worker_id, error: error.message }); + logger.error('근태 기록 저장 실패', { record_date, user_id, error: error.message }); throw new DatabaseError('근태 기록 저장 중 데이터베이스 오류가 발생했습니다'); } }; @@ -173,28 +173,28 @@ const upsertAttendanceRecordService = async (recordData) => { * 휴가 처리 */ const processVacationService = async (vacationData) => { - const { record_date, worker_id, vacation_type_id } = vacationData; + const { record_date, user_id, vacation_type_id } = vacationData; - if (!record_date || !worker_id || !vacation_type_id) { + if (!record_date || !user_id || !vacation_type_id) { throw new ValidationError('필수 필드가 누락되었습니다', { - required: ['record_date', 'worker_id', 'vacation_type_id'], - received: { record_date, worker_id, vacation_type_id } + required: ['record_date', 'user_id', 'vacation_type_id'], + received: { record_date, user_id, vacation_type_id } }); } - logger.info('휴가 처리 요청', { record_date, worker_id, vacation_type_id }); + logger.info('휴가 처리 요청', { record_date, user_id, vacation_type_id }); try { const result = await AttendanceModel.processVacation({ record_date, - worker_id, + user_id, vacation_type_id }); - logger.info('휴가 처리 성공', { record_date, worker_id }); + logger.info('휴가 처리 성공', { record_date, user_id }); return result; } catch (error) { - logger.error('휴가 처리 실패', { record_date, worker_id, error: error.message }); + logger.error('휴가 처리 실패', { record_date, user_id, error: error.message }); throw new DatabaseError('휴가 처리 중 데이터베이스 오류가 발생했습니다'); } }; @@ -203,28 +203,28 @@ const processVacationService = async (vacationData) => { * 초과 근무 승인 */ const approveOvertimeService = async (overtimeData) => { - const { record_date, worker_id, overtime_approved } = overtimeData; + const { record_date, user_id, overtime_approved } = overtimeData; - if (!record_date || !worker_id || overtime_approved === undefined) { + if (!record_date || !user_id || overtime_approved === undefined) { throw new ValidationError('필수 필드가 누락되었습니다', { - required: ['record_date', 'worker_id', 'overtime_approved'], - received: { record_date, worker_id, overtime_approved } + required: ['record_date', 'user_id', 'overtime_approved'], + received: { record_date, user_id, overtime_approved } }); } - logger.info('초과 근무 승인 요청', { record_date, worker_id, overtime_approved }); + logger.info('초과 근무 승인 요청', { record_date, user_id, overtime_approved }); try { const result = await AttendanceModel.approveOvertime({ record_date, - worker_id, + user_id, overtime_approved }); - logger.info('초과 근무 승인 처리 성공', { record_date, worker_id }); + logger.info('초과 근무 승인 처리 성공', { record_date, user_id }); return result; } catch (error) { - logger.error('초과 근무 승인 실패', { record_date, worker_id, error: error.message }); + logger.error('초과 근무 승인 실패', { record_date, user_id, error: error.message }); throw new DatabaseError('초과 근무 승인 중 데이터베이스 오류가 발생했습니다'); } }; @@ -264,22 +264,22 @@ const getVacationTypesService = async () => { /** * 작업자 휴가 잔여 일수 조회 */ -const getWorkerVacationBalanceService = async (workerId) => { - if (!workerId) { +const getWorkerVacationBalanceService = async (userId) => { + if (!userId) { throw new ValidationError('작업자 ID가 필요합니다', { - required: ['worker_id'], - received: { workerId } + required: ['user_id'], + received: { userId } }); } - logger.info('휴가 잔여 일수 조회 요청', { workerId }); + logger.info('휴가 잔여 일수 조회 요청', { userId }); try { - const balance = await AttendanceModel.getWorkerVacationBalance(workerId); - logger.info('휴가 잔여 일수 조회 성공', { workerId }); + const balance = await AttendanceModel.getWorkerVacationBalance(userId); + logger.info('휴가 잔여 일수 조회 성공', { userId }); return balance; } catch (error) { - logger.error('휴가 잔여 일수 조회 실패', { workerId, error: error.message }); + logger.error('휴가 잔여 일수 조회 실패', { userId, error: error.message }); throw new DatabaseError('휴가 잔여 일수 조회 중 데이터베이스 오류가 발생했습니다'); } }; @@ -287,7 +287,7 @@ const getWorkerVacationBalanceService = async (workerId) => { /** * 월별 근태 통계 조회 */ -const getMonthlyAttendanceStatsService = async (year, month, workerId = null) => { +const getMonthlyAttendanceStatsService = async (year, month, userId = null) => { if (!year || !month) { throw new ValidationError('연도와 월이 필요합니다', { required: ['year', 'month'], @@ -295,10 +295,10 @@ const getMonthlyAttendanceStatsService = async (year, month, workerId = null) => }); } - logger.info('월별 근태 통계 조회 요청', { year, month, workerId }); + logger.info('월별 근태 통계 조회 요청', { year, month, userId }); try { - const stats = await AttendanceModel.getMonthlyAttendanceStats(year, month, workerId); + const stats = await AttendanceModel.getMonthlyAttendanceStats(year, month, userId); logger.info('월별 근태 통계 조회 성공', { year, month }); return stats; } catch (error) { @@ -347,21 +347,21 @@ const saveCheckinsService = async (date, checkins) => { const results = []; for (const checkin of checkins) { - const { worker_id, is_present } = checkin; + const { user_id, is_present } = checkin; - if (!worker_id || is_present === undefined) { + if (!user_id || is_present === undefined) { logger.warn('출근 체크 데이터 누락', { checkin }); continue; } const result = await AttendanceModel.upsertCheckin({ - worker_id, + user_id, record_date: date, is_present }); results.push({ - worker_id, + user_id, record_id: result, is_present }); diff --git a/system1-factory/api/services/auth.service.js b/system1-factory/api/services/auth.service.js index a7481b4..ce013b6 100644 --- a/system1-factory/api/services/auth.service.js +++ b/system1-factory/api/services/auth.service.js @@ -57,7 +57,7 @@ const loginService = async (username, password, ipAddress, userAgent) => { const token = jwt.sign( - { user_id: user.user_id, username: user.username, role: user.role_name, role_id: user.role_id, access_level: user.access_level, worker_id: user.worker_id, name: user.name || user.username }, + { user_id: user.user_id, username: user.username, role: user.role_name, role_id: user.role_id, access_level: user.access_level, name: user.name || user.username }, process.env.JWT_SECRET || 'your-secret-key', { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } ); @@ -81,8 +81,7 @@ const loginService = async (username, password, ipAddress, userAgent) => { username: user.username, name: user.name || user.username, role: user.role_name, - access_level: user.access_level, - worker_id: user.worker_id + access_level: user.access_level } } }; diff --git a/system1-factory/api/services/dailyIssueReportService.js b/system1-factory/api/services/dailyIssueReportService.js index f8e8f92..e776c63 100644 --- a/system1-factory/api/services/dailyIssueReportService.js +++ b/system1-factory/api/services/dailyIssueReportService.js @@ -22,23 +22,23 @@ const logger = require('../utils/logger'); * @param {string} issueData.start_time - 이슈 시작 시간 * @param {string} issueData.end_time - 이슈 종료 시간 * @param {number} issueData.issue_type_id - 이슈 유형 ID - * @param {number[]} issueData.worker_ids - 작업자 ID 배열 + * @param {number[]} issueData.user_ids - 작업자 ID 배열 * @returns {Promise} 생성 결과 */ const createDailyIssueReportService = async (issueData) => { - const { date, project_id, start_time, end_time, issue_type_id, worker_ids } = issueData; + const { date, project_id, start_time, end_time, issue_type_id, user_ids } = issueData; // 필수 필드 검증 - if (!date || !project_id || !start_time || !end_time || !issue_type_id || !worker_ids) { + if (!date || !project_id || !start_time || !end_time || !issue_type_id || !user_ids) { throw new ValidationError('필수 필드가 누락되었습니다', { - required: ['date', 'project_id', 'start_time', 'end_time', 'issue_type_id', 'worker_ids'], - received: { date, project_id, start_time, end_time, issue_type_id, worker_ids: !!worker_ids } + required: ['date', 'project_id', 'start_time', 'end_time', 'issue_type_id', 'user_ids'], + received: { date, project_id, start_time, end_time, issue_type_id, user_ids: !!user_ids } }); } - if (!Array.isArray(worker_ids) || worker_ids.length === 0) { - throw new ValidationError('worker_ids는 최소 한 명 이상의 작업자를 포함하는 배열이어야 합니다', { - received: { worker_ids, isArray: Array.isArray(worker_ids), length: worker_ids?.length } + if (!Array.isArray(user_ids) || user_ids.length === 0) { + throw new ValidationError('user_ids는 최소 한 명 이상의 작업자를 포함하는 배열이어야 합니다', { + received: { user_ids, isArray: Array.isArray(user_ids), length: user_ids?.length } }); } @@ -46,17 +46,17 @@ const createDailyIssueReportService = async (issueData) => { date, project_id, issue_type_id, - worker_count: worker_ids.length + worker_count: user_ids.length }); // 모델에 전달할 데이터 준비 - const reportsToCreate = worker_ids.map(worker_id => ({ + const reportsToCreate = user_ids.map(user_id => ({ date, project_id, start_time, end_time, issue_type_id, - worker_id + user_id })); try { @@ -75,7 +75,7 @@ const createDailyIssueReportService = async (issueData) => { logger.error('이슈 보고서 생성 실패', { date, project_id, - worker_ids, + user_ids, error: error.message }); throw new DatabaseError('이슈 보고서 생성 중 오류가 발생했습니다'); diff --git a/system1-factory/api/services/dailyWorkReportService.js b/system1-factory/api/services/dailyWorkReportService.js index 5b058a3..b29ff11 100644 --- a/system1-factory/api/services/dailyWorkReportService.js +++ b/system1-factory/api/services/dailyWorkReportService.js @@ -17,13 +17,13 @@ const logger = require('../utils/logger'); * @returns {Promise} 생성 결과 또는 에러 */ const createDailyWorkReportService = async (reportData) => { - const { report_date, worker_id, work_entries, created_by, created_by_name } = reportData; + const { report_date, user_id, work_entries, created_by, created_by_name } = reportData; // 1. 기본 유효성 검사 - if (!report_date || !worker_id || !work_entries) { + if (!report_date || !user_id || !work_entries) { throw new ValidationError('필수 필드가 누락되었습니다', { - required: ['report_date', 'worker_id', 'work_entries'], - received: { report_date, worker_id, work_entries: !!work_entries } + required: ['report_date', 'user_id', 'work_entries'], + received: { report_date, user_id, work_entries: !!work_entries } }); } @@ -70,7 +70,7 @@ const createDailyWorkReportService = async (reportData) => { // 3. 모델에 전달할 데이터 준비 const modelData = { report_date, - worker_id: parseInt(worker_id), + user_id: parseInt(user_id), entries: work_entries.map(entry => ({ project_id: entry.project_id, work_type_id: entry.task_id, // task_id를 work_type_id로 매핑 @@ -83,7 +83,7 @@ const createDailyWorkReportService = async (reportData) => { logger.info('작업보고서 생성 요청', { date: report_date, - worker: worker_id, + worker: user_id, creator: created_by_name, entries_count: modelData.entries.length }); @@ -117,7 +117,7 @@ const createDailyWorkReportService = async (reportData) => { * @returns {Promise} 조회된 작업 보고서 배열 */ const getDailyWorkReportsService = async (queryParams, userInfo) => { - const { date, start_date, end_date, worker_id, created_by: requested_created_by, view_all } = queryParams; + const { date, start_date, end_date, user_id, created_by: requested_created_by, view_all } = queryParams; const { user_id: current_user_id, role } = userInfo; // 날짜 또는 날짜 범위 중 하나는 필수 @@ -142,8 +142,8 @@ const getDailyWorkReportsService = async (queryParams, userInfo) => { options.end_date = end_date; } - if (worker_id) { - options.worker_id = parseInt(worker_id); + if (user_id) { + options.user_id = parseInt(user_id); } // 최종적으로 필터링할 작성자 ID 결정 @@ -285,16 +285,16 @@ const getStatisticsService = async (queryParams) => { /** * 일일 또는 작업자별 작업 요약 정보를 조회하는 비즈니스 로직을 처리합니다. - * @param {object} queryParams - 컨트롤러에서 전달된 쿼리 파라미터 (date 또는 worker_id) + * @param {object} queryParams - 컨트롤러에서 전달된 쿼리 파라미터 (date 또는 user_id) * @returns {Promise} 요약 데이터 */ const getSummaryService = async (queryParams) => { - const { date, worker_id } = queryParams; + const { date, user_id } = queryParams; - if (!date && !worker_id) { + if (!date && !user_id) { throw new ValidationError('날짜 또는 작업자 ID가 필요합니다', { - required: 'date OR worker_id', - received: { date, worker_id } + required: 'date OR user_id', + received: { date, user_id } }); } @@ -304,10 +304,10 @@ const getSummaryService = async (queryParams) => { const result = await dailyWorkReportModel.getSummaryByDate(date); logger.info('일일 요약 조회 성공', { date }); return result; - } else { // worker_id - logger.info('작업자별 요약 조회 요청', { worker_id }); - const result = await dailyWorkReportModel.getSummaryByWorker(worker_id); - logger.info('작업자별 요약 조회 성공', { worker_id }); + } else { // user_id + logger.info('작업자별 요약 조회 요청', { user_id }); + const result = await dailyWorkReportModel.getSummaryByWorker(user_id); + logger.info('작업자별 요약 조회 성공', { user_id }); return result; } } catch (error) { diff --git a/system1-factory/api/utils/queryOptimizer.js b/system1-factory/api/utils/queryOptimizer.js index e716d0f..d882dae 100644 --- a/system1-factory/api/utils/queryOptimizer.js +++ b/system1-factory/api/utils/queryOptimizer.js @@ -286,7 +286,7 @@ const optimizedQueries = { let baseQuery = ` SELECT w.*, d.department_name, COUNT(dwr.id) as report_count FROM workers w - LEFT JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id + LEFT JOIN daily_work_reports dwr ON w.user_id = dwr.user_id LEFT JOIN departments d ON w.department_id = d.department_id `; @@ -322,10 +322,10 @@ const optimizedQueries = { countQuery += whereClause; } - baseQuery += ' GROUP BY w.worker_id'; + baseQuery += ' GROUP BY w.user_id'; return executePagedQuery(baseQuery, countQuery, params, { - page, limit, orderBy: 'w.worker_id', orderDirection: 'DESC' + page, limit, orderBy: 'w.user_id', orderDirection: 'DESC' }); }, @@ -362,7 +362,7 @@ const optimizedQueries = { wt.name as work_type_name, wst.name as work_status_name, et.name as error_type_name, u.name as created_by_name FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.worker_id = w.worker_id + LEFT JOIN workers w ON dwr.user_id = w.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id LEFT JOIN work_status_types wst ON dwr.work_status_id = wst.id diff --git a/system1-factory/api/utils/validator.js b/system1-factory/api/utils/validator.js index 66d4f3d..2a776b2 100644 --- a/system1-factory/api/utils/validator.js +++ b/system1-factory/api/utils/validator.js @@ -242,14 +242,14 @@ const schemas = { password: { required: true, password: true }, name: { required: true, type: 'string', minLength: 2, maxLength: 100 }, access_level: { required: true, enum: ['user', 'admin', 'system'] }, - worker_id: { type: 'integer' } + user_id: { type: 'integer' } }, // 사용자 업데이트 updateUser: { name: { type: 'string', minLength: 2, maxLength: 100 }, access_level: { enum: ['user', 'admin', 'system'] }, - worker_id: { type: 'integer' } + user_id: { type: 'integer' } }, // 비밀번호 변경 @@ -261,7 +261,7 @@ const schemas = { // 일일 작업 보고서 생성 (배열 형태) createDailyWorkReport: { report_date: { required: true, type: 'date' }, - worker_id: { required: true, type: 'integer' }, + user_id: { required: true, type: 'integer' }, work_entries: { required: true, type: 'array' }, created_by: { type: 'integer' } }, diff --git a/system1-factory/web/js/_deprecated/index.js b/system1-factory/web/js/_deprecated/index.js new file mode 100644 index 0000000..c51fc59 --- /dev/null +++ b/system1-factory/web/js/_deprecated/index.js @@ -0,0 +1,318 @@ +/** + * Daily Work Report - Module Loader + * 작업보고서 모듈을 초기화하고 연결하는 메인 진입점 + * + * 로드 순서: + * 1. state.js - 전역 상태 관리 + * 2. utils.js - 유틸리티 함수 + * 3. api.js - API 클라이언트 + * 4. index.js - 이 파일 (메인 컨트롤러) + */ + +class DailyWorkReportController { + constructor() { + this.state = window.DailyWorkReportState; + this.api = window.DailyWorkReportAPI; + this.utils = window.DailyWorkReportUtils; + this.initialized = false; + + console.log('[Controller] DailyWorkReportController 생성'); + } + + /** + * 초기화 + */ + async init() { + if (this.initialized) { + console.log('[Controller] 이미 초기화됨'); + return; + } + + console.log('[Controller] 초기화 시작...'); + + try { + // 이벤트 리스너 설정 + this.setupEventListeners(); + + // 기본 데이터 로드 + await this.api.loadAllData(); + + // TBM 탭이 기본 + await this.switchTab('tbm'); + + this.initialized = true; + console.log('[Controller] 초기화 완료'); + + } catch (error) { + console.error('[Controller] 초기화 실패:', error); + window.showMessage?.('초기화 중 오류가 발생했습니다: ' + error.message, 'error'); + } + } + + /** + * 이벤트 리스너 설정 + */ + setupEventListeners() { + // 탭 버튼 + const tbmBtn = document.getElementById('tbmReportTab'); + const completedBtn = document.getElementById('completedReportTab'); + + if (tbmBtn) { + tbmBtn.addEventListener('click', () => this.switchTab('tbm')); + } + if (completedBtn) { + completedBtn.addEventListener('click', () => this.switchTab('completed')); + } + + // 완료 보고서 날짜 변경 + const completedDateInput = document.getElementById('completedReportDate'); + if (completedDateInput) { + completedDateInput.addEventListener('change', () => this.loadCompletedReports()); + } + + console.log('[Controller] 이벤트 리스너 설정 완료'); + } + + /** + * 탭 전환 + */ + async switchTab(tab) { + this.state.setCurrentTab(tab); + + const tbmBtn = document.getElementById('tbmReportTab'); + const completedBtn = document.getElementById('completedReportTab'); + const tbmSection = document.getElementById('tbmReportSection'); + const completedSection = document.getElementById('completedReportSection'); + + // 모든 탭 버튼 비활성화 + tbmBtn?.classList.remove('active'); + completedBtn?.classList.remove('active'); + + // 모든 섹션 숨기기 + if (tbmSection) tbmSection.style.display = 'none'; + if (completedSection) completedSection.style.display = 'none'; + + // 선택된 탭 활성화 + if (tab === 'tbm') { + tbmBtn?.classList.add('active'); + if (tbmSection) tbmSection.style.display = 'block'; + await this.loadTbmData(); + } else if (tab === 'completed') { + completedBtn?.classList.add('active'); + if (completedSection) completedSection.style.display = 'block'; + + // 오늘 날짜로 초기화 + const dateInput = document.getElementById('completedReportDate'); + if (dateInput) { + dateInput.value = this.utils.getKoreaToday(); + } + await this.loadCompletedReports(); + } + } + + /** + * TBM 데이터 로드 + */ + async loadTbmData() { + try { + await this.api.loadIncompleteTbms(); + await this.api.loadDailyIssuesForTbms(); + + // 렌더링은 기존 함수 사용 (점진적 마이그레이션) + if (typeof window.renderTbmWorkList === 'function') { + window.renderTbmWorkList(); + } + } catch (error) { + console.error('[Controller] TBM 데이터 로드 오류:', error); + window.showMessage?.('TBM 데이터를 불러오는 중 오류가 발생했습니다.', 'error'); + } + } + + /** + * 완료 보고서 로드 + */ + async loadCompletedReports() { + try { + const dateInput = document.getElementById('completedReportDate'); + const date = dateInput?.value || this.utils.getKoreaToday(); + + const reports = await this.api.loadCompletedReports(date); + + // 렌더링은 기존 함수 사용 + if (typeof window.renderCompletedReports === 'function') { + window.renderCompletedReports(reports); + } + } catch (error) { + console.error('[Controller] 완료 보고서 로드 오류:', error); + window.showMessage?.('완료 보고서를 불러오는 중 오류가 발생했습니다.', 'error'); + } + } + + /** + * TBM 작업보고서 제출 + */ + async submitTbmWorkReport(index) { + try { + const tbm = this.state.incompleteTbms[index]; + if (!tbm) { + throw new Error('TBM 데이터를 찾을 수 없습니다.'); + } + + // 유효성 검사 + const totalHoursInput = document.getElementById(`totalHours_${index}`); + const totalHours = parseFloat(totalHoursInput?.value); + + if (!totalHours || totalHours <= 0) { + window.showMessage?.('작업시간을 입력해주세요.', 'warning'); + return; + } + + // 부적합 시간 계산 + const defects = this.state.tempDefects[index] || []; + const errorHours = defects.reduce((sum, d) => sum + (parseFloat(d.defect_hours) || 0), 0); + const regularHours = totalHours - errorHours; + + if (regularHours < 0) { + window.showMessage?.('부적합 시간이 총 작업시간을 초과할 수 없습니다.', 'warning'); + return; + } + + // API 데이터 구성 + const user = this.state.getCurrentUser(); + const reportData = { + tbm_session_id: tbm.session_id, + tbm_assignment_id: tbm.assignment_id, + user_id: tbm.user_id, + project_id: tbm.project_id, + work_type_id: tbm.work_type_id, + report_date: this.utils.formatDateForApi(tbm.session_date), + total_hours: totalHours, + regular_hours: regularHours, + error_hours: errorHours, + work_status_id: errorHours > 0 ? 2 : 1, + created_by: user?.user_id || user?.id, + defects: defects.map(d => ({ + category_id: d.category_id, + item_id: d.item_id, + issue_report_id: d.issue_report_id, + defect_hours: d.defect_hours, + note: d.note + })) + }; + + const result = await this.api.submitTbmWorkReport(reportData); + + window.showSaveResultModal?.( + 'success', + '제출 완료', + `${tbm.worker_name}의 작업보고서가 제출되었습니다.` + ); + + // 목록 새로고침 + await this.loadTbmData(); + + } catch (error) { + console.error('[Controller] 제출 오류:', error); + window.showSaveResultModal?.( + 'error', + '제출 실패', + error.message || '작업보고서 제출 중 오류가 발생했습니다.' + ); + } + } + + /** + * 세션 일괄 제출 + */ + async batchSubmitSession(sessionKey) { + const rows = document.querySelectorAll(`tr[data-session-key="${sessionKey}"][data-type="tbm"]`); + const indices = []; + + rows.forEach(row => { + const index = parseInt(row.dataset.index); + const totalHoursInput = document.getElementById(`totalHours_${index}`); + if (totalHoursInput?.value && parseFloat(totalHoursInput.value) > 0) { + indices.push(index); + } + }); + + if (indices.length === 0) { + window.showMessage?.('제출할 항목이 없습니다. 작업시간을 입력해주세요.', 'warning'); + return; + } + + const confirmed = confirm(`${indices.length}건의 작업보고서를 일괄 제출하시겠습니까?`); + if (!confirmed) return; + + let successCount = 0; + let failCount = 0; + + for (const index of indices) { + try { + await this.submitTbmWorkReport(index); + successCount++; + } catch (error) { + failCount++; + console.error(`[Controller] 일괄 제출 오류 (index: ${index}):`, error); + } + } + + if (failCount === 0) { + window.showSaveResultModal?.('success', '일괄 제출 완료', `${successCount}건이 성공적으로 제출되었습니다.`); + } else { + window.showSaveResultModal?.('warning', '일괄 제출 부분 완료', `성공: ${successCount}건, 실패: ${failCount}건`); + } + } + + /** + * 상태 디버그 + */ + debug() { + console.log('[Controller] 상태 디버그:'); + this.state.debug(); + } +} + +// 전역 인스턴스 생성 +window.DailyWorkReportController = new DailyWorkReportController(); + +// 하위 호환성: 기존 전역 함수들 +window.switchTab = (tab) => window.DailyWorkReportController.switchTab(tab); +window.submitTbmWorkReport = (index) => window.DailyWorkReportController.submitTbmWorkReport(index); +window.batchSubmitTbmSession = (sessionKey) => window.DailyWorkReportController.batchSubmitSession(sessionKey); + +// 사용자 정보 함수 +window.getUser = () => window.DailyWorkReportState.getUser(); +window.getCurrentUser = () => window.DailyWorkReportState.getCurrentUser(); + +// 날짜 그룹 토글 (UI 함수) +window.toggleDateGroup = function(dateStr) { + const group = document.querySelector(`.date-group[data-date="${dateStr}"]`); + if (!group) return; + + const isExpanded = group.classList.contains('expanded'); + const content = group.querySelector('.date-group-content'); + const icon = group.querySelector('.date-toggle-icon'); + + if (isExpanded) { + group.classList.remove('expanded'); + group.classList.add('collapsed'); + if (content) content.style.display = 'none'; + if (icon) icon.textContent = '▶'; + } else { + group.classList.remove('collapsed'); + group.classList.add('expanded'); + if (content) content.style.display = 'block'; + if (icon) icon.textContent = '▼'; + } +}; + +// DOMContentLoaded 이벤트에서 초기화 +document.addEventListener('DOMContentLoaded', () => { + // 약간의 지연 후 초기화 (다른 스크립트 로드 대기) + setTimeout(() => { + window.DailyWorkReportController.init(); + }, 100); +}); + +console.log('[Module] daily-work-report/index.js 로드 완료'); diff --git a/system1-factory/web/js/_deprecated/work-report-api.js b/system1-factory/web/js/_deprecated/work-report-api.js new file mode 100644 index 0000000..3b23ec9 --- /dev/null +++ b/system1-factory/web/js/_deprecated/work-report-api.js @@ -0,0 +1,51 @@ +// /js/work-report-api.js +import { apiGet, apiPost } from './api-helper.js'; + +/** + * 작업 보고서 작성을 위해 필요한 초기 데이터(작업자, 프로젝트, 태스크)를 가져옵니다. + * Promise.all을 사용하여 병렬로 API를 호출합니다. + * @returns {Promise<{workers: Array, projects: Array, tasks: Array}>} + */ +export async function getInitialData() { + try { + const [allWorkers, projects, tasks] = await Promise.all([ + apiGet('/workers'), + apiGet('/projects'), + apiGet('/tasks') + ]); + + // 활성화된 작업자만 필터링 + const workers = allWorkers.filter(worker => { + return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true; + }); + + // 데이터 형식 검증 + if (!Array.isArray(workers) || !Array.isArray(projects) || !Array.isArray(tasks)) { + throw new Error('서버에서 받은 데이터 형식이 올바르지 않습니다.'); + } + + // 작업자 목록은 ID 기준으로 정렬 + workers.sort((a, b) => a.user_id - b.user_id); + + return { workers, projects, tasks }; + } catch (error) { + console.error('초기 데이터 로딩 중 오류 발생:', error); + // 에러를 다시 던져서 호출한 쪽에서 처리할 수 있도록 함 + throw error; + } +} + +/** + * 작성된 작업 보고서 데이터를 서버에 전송합니다. + * @param {Array} reportData - 전송할 작업 보고서 데이터 배열 + * @returns {Promise} - 서버의 응답 결과 + */ +export async function createWorkReport(reportData) { + try { + const result = await apiPost('/workreports', reportData); + return result; + } catch (error) { + console.error('작업 보고서 생성 요청 실패:', error); + throw error; + } +} \ No newline at end of file diff --git a/system1-factory/web/js/_deprecated/work-report-create.js b/system1-factory/web/js/_deprecated/work-report-create.js new file mode 100644 index 0000000..d61ed79 --- /dev/null +++ b/system1-factory/web/js/_deprecated/work-report-create.js @@ -0,0 +1,79 @@ +// /js/work-report-create.js +import { renderCalendar } from './calendar.js'; +import { getInitialData, createWorkReport } from './work-report-api.js'; +import { initializeReportTable, getReportData } from './work-report-ui.js'; + +// 전역 상태 변수 +let selectedDate = ''; + +/** + * 날짜가 선택되었을 때 실행되는 콜백 함수. + * 초기 데이터를 로드하고 테이블을 렌더링합니다. + * @param {string} date - 선택된 날짜 (YYYY-MM-DD 형식) + */ +async function onDateSelect(date) { + selectedDate = date; + const tableBody = document.getElementById('reportBody'); + tableBody.innerHTML = '데이터를 불러오는 중...'; + + try { + const initialData = await getInitialData(); + initializeReportTable(initialData); + } catch (error) { + alert('데이터를 불러오는 데 실패했습니다: ' + error.message); + tableBody.innerHTML = '오류 발생! 데이터를 불러올 수 없습니다.'; + } +} + +/** + * '전체 등록' 버튼 클릭 시 실행되는 이벤트 핸들러. + * 폼 데이터를 서버에 전송합니다. + */ +async function handleSubmit() { + if (!selectedDate) { + alert('먼저 달력에서 날짜를 선택해주세요.'); + return; + } + + const reportData = getReportData(); + if (!reportData) { + // getReportData 내부에서 이미 alert으로 사용자에게 알림 + return; + } + + // 각 항목에 선택된 날짜 추가 + const payload = reportData.map(item => ({ ...item, date: selectedDate })); + + const submitBtn = document.getElementById('submitBtn'); + submitBtn.disabled = true; + submitBtn.textContent = '등록 중...'; + + try { + const result = await createWorkReport(payload); + if (result.success) { + alert('✅ 작업 보고서가 성공적으로 등록되었습니다!'); + // 성공 후 폼을 다시 로드하거나, 다른 페이지로 이동 등의 로직 추가 가능 + onDateSelect(selectedDate); // 현재 날짜의 폼을 다시 로드 + } else { + throw new Error(result.error || '알 수 없는 오류로 등록에 실패했습니다.'); + } + } catch (error) { + alert('❌ 등록 실패: ' + error.message); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = '전체 등록'; + } +} + +/** + * 페이지 초기화 함수 + */ +function initializePage() { + renderCalendar('calendar', onDateSelect); + + const submitBtn = document.getElementById('submitBtn'); + submitBtn.addEventListener('click', handleSubmit); +} + +// DOM이 로드되면 페이지 초기화를 시작합니다. +document.addEventListener('DOMContentLoaded', initializePage); \ No newline at end of file diff --git a/system1-factory/web/js/_deprecated/work-report-ui.js b/system1-factory/web/js/_deprecated/work-report-ui.js new file mode 100644 index 0000000..e4eba20 --- /dev/null +++ b/system1-factory/web/js/_deprecated/work-report-ui.js @@ -0,0 +1,141 @@ +// /js/work-report-ui.js + +const DEFAULT_PROJECT_ID = '13'; // 나중에는 API나 설정에서 받아오는 것이 좋음 +const DEFAULT_TASK_ID = '15'; + +/** + * 주어진 데이터를 바탕으로 + ${worker.worker_name} + + + + + + + + + + + + `; + + // 이벤트 리스너 설정 + const workTypeSelect = tr.querySelector('[name="work_type"]'); + const projectSelect = tr.querySelector('[name="project_id"]'); + const taskSelect = tr.querySelector('[name="task_id"]'); + + workTypeSelect.addEventListener('change', () => { + const isDisabled = ['연차', '휴무', '유급'].includes(workTypeSelect.value); + projectSelect.disabled = isDisabled; + taskSelect.disabled = isDisabled; + if (isDisabled) { + projectSelect.value = DEFAULT_PROJECT_ID; + taskSelect.value = DEFAULT_TASK_ID; + } + }); + + tr.querySelector('.remove-btn').addEventListener('click', () => { + tr.remove(); + updateRowNumbers(tr.parentElement); + }); + + return tr; +} + +/** + * 작업 보고서 테이블을 초기화하고 데이터를 채웁니다. + * @param {{workers: Array, projects: Array, tasks: Array}} initialData - 초기 데이터 + */ +export function initializeReportTable(initialData) { + const tableBody = document.getElementById('reportBody'); + if (!tableBody) return; + + tableBody.innerHTML = ''; // 기존 내용 초기화 + const { workers, projects, tasks } = initialData; + + if (!workers || workers.length === 0) { + tableBody.innerHTML = '등록할 작업자 정보가 없습니다.'; + return; + } + + workers.forEach((worker, index) => { + const row = createReportRow(worker, projects, tasks, index); + tableBody.appendChild(row); + }); +} + +/** + * 테이블에서 폼 데이터를 추출하여 배열로 반환합니다. + * @returns {Array|null} - 추출된 데이터 배열 또는 유효성 검사 실패 시 null + */ +export function getReportData() { + const tableBody = document.getElementById('reportBody'); + const rows = tableBody.querySelectorAll('tr'); + + if (rows.length === 0 || (rows.length === 1 && rows[0].cells.length < 2)) { + alert('등록할 내용이 없습니다.'); + return null; + } + + const reportData = []; + const workerIds = new Set(); + + for (const tr of rows) { + const workerId = tr.querySelector('[name="user_id"]').value; + if (workerIds.has(workerId)) { + alert(`오류: 작업자 '${tr.cells[1].textContent.trim()}'가 중복 등록되었습니다.`); + return null; + } + workerIds.add(workerId); + + reportData.push({ + user_id: workerId, + project_id: tr.querySelector('[name="project_id"]').value, + task_id: tr.querySelector('[name="task_id"]').value, + overtime_hours: tr.querySelector('[name="overtime"]').value || 0, + work_details: tr.querySelector('[name="work_type"]').value, + memo: tr.querySelector('[name="memo"]').value + }); + } + + return reportData; +} \ No newline at end of file diff --git a/system1-factory/web/js/admin-settings.js b/system1-factory/web/js/admin-settings.js index 8b8b820..e802cc6 100644 --- a/system1-factory/web/js/admin-settings.js +++ b/system1-factory/web/js/admin-settings.js @@ -794,14 +794,14 @@ document.addEventListener('DOMContentLoaded', () => { // ========== 작업자 연결 기능 ========== // let departments = []; -let selectedWorkerId = null; +let selectedUserId = null; // 연결된 작업자 정보 표시 업데이트 function updateLinkedWorkerDisplay(user) { const linkedWorkerInfo = document.getElementById('linkedWorkerInfo'); if (!linkedWorkerInfo) return; - if (user.worker_id && user.worker_name) { + if (user.user_id && user.worker_name) { linkedWorkerInfo.innerHTML = ` 👤 ${user.worker_name} @@ -820,7 +820,7 @@ async function openWorkerSelectModal() { return; } - selectedWorkerId = currentEditingUser.worker_id || null; + selectedUserId = currentEditingUser.user_id || null; // 부서 목록 로드 await loadDepartmentsForSelect(); @@ -833,7 +833,7 @@ window.openWorkerSelectModal = openWorkerSelectModal; // 작업자 선택 모달 닫기 function closeWorkerSelectModal() { document.getElementById('workerSelectModal').style.display = 'none'; - selectedWorkerId = null; + selectedUserId = null; } window.closeWorkerSelectModal = closeWorkerSelectModal; @@ -906,18 +906,18 @@ function renderWorkerListForSelect(workers) { } // 이미 다른 계정에 연결된 작업자 확인을 위해 users 배열 사용 - const linkedWorkerIds = users - .filter(u => u.worker_id && u.user_id !== currentEditingUser?.user_id) - .map(u => u.worker_id); + const linkedUserIds = users + .filter(u => u.user_id && u.user_id !== currentEditingUser?.user_id) + .map(u => u.user_id); container.innerHTML = workers.map(worker => { - const isSelected = selectedWorkerId === worker.worker_id; - const isLinkedToOther = linkedWorkerIds.includes(worker.worker_id); - const linkedUser = isLinkedToOther ? users.find(u => u.worker_id === worker.worker_id) : null; + const isSelected = selectedUserId === worker.user_id; + const isLinkedToOther = linkedUserIds.includes(worker.user_id); + const linkedUser = isLinkedToOther ? users.find(u => u.user_id === worker.user_id) : null; return `
+ onclick="${isLinkedToOther ? '' : `selectWorker(${worker.user_id}, '${worker.worker_name}')`}">
${worker.worker_name.charAt(0)}
${worker.worker_name}
@@ -941,8 +941,8 @@ function getJobTypeName(jobType) { } // 작업자 선택 -async function selectWorker(workerId, workerName) { - selectedWorkerId = workerId; +async function selectWorker(userId, workerName) { + selectedUserId = userId; // UI 업데이트 document.querySelectorAll('.worker-select-item').forEach(item => { @@ -950,7 +950,7 @@ async function selectWorker(workerId, workerName) { item.querySelector('.select-indicator').textContent = ''; }); - const selectedItem = document.querySelector(`.worker-select-item[onclick*="${workerId}"]`); + const selectedItem = document.querySelector(`.worker-select-item[onclick*="${userId}"]`); if (selectedItem) { selectedItem.classList.add('selected'); selectedItem.querySelector('.select-indicator').textContent = '✓'; @@ -959,12 +959,12 @@ async function selectWorker(workerId, workerName) { // 서버에 저장 try { const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', { - worker_id: workerId + user_id: userId }); if (response.success) { // currentEditingUser 업데이트 - currentEditingUser.worker_id = workerId; + currentEditingUser.user_id = userId; currentEditingUser.worker_name = workerName; // 부서 정보도 업데이트 @@ -1003,7 +1003,7 @@ async function unlinkWorker() { return; } - if (!currentEditingUser.worker_id) { + if (!currentEditingUser.user_id) { showToast('연결된 작업자가 없습니다.', 'warning'); closeWorkerSelectModal(); return; @@ -1015,19 +1015,19 @@ async function unlinkWorker() { try { const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', { - worker_id: null + user_id: null }); if (response.success) { // currentEditingUser 업데이트 - currentEditingUser.worker_id = null; + currentEditingUser.user_id = null; currentEditingUser.worker_name = null; currentEditingUser.department_name = null; // users 배열 업데이트 const userIndex = users.findIndex(u => u.user_id === currentEditingUser.user_id); if (userIndex !== -1) { - users[userIndex] = { ...users[userIndex], worker_id: null, worker_name: null, department_name: null }; + users[userIndex] = { ...users[userIndex], user_id: null, worker_name: null, department_name: null }; } // 표시 업데이트 diff --git a/system1-factory/web/js/attendance-validation.js b/system1-factory/web/js/attendance-validation.js index a2f47e7..b9f5244 100644 --- a/system1-factory/web/js/attendance-validation.js +++ b/system1-factory/web/js/attendance-validation.js @@ -322,7 +322,7 @@ async function fetchDailyWorkReports(date) { async function updateWorkerHours(workerId, date, newHours, reason = '') { try { const data = { - worker_id: workerId, + user_id: workerId, report_date: date, work_hours: parseFloat(newHours), modification_reason: reason, @@ -412,7 +412,7 @@ async function calculateDateStatus(dateStr) { status = 'missing'; } else { const hasDiscrepancy = workReports.some(wr => { - const dr = dailyReports.find(d => d.worker_id === wr.worker_id); + const dr = dailyReports.find(d => d.user_id === wr.user_id); if (!dr) return true; const expected = calculateExpectedHours(wr.status, wr.overtime_hours); @@ -607,8 +607,8 @@ async function getWorkersForDate(dateStr) { // WorkReports 데이터 추가 workReports.forEach(wr => { - workerMap.set(wr.worker_id, { - worker_id: wr.worker_id, + workerMap.set(wr.user_id, { + user_id: wr.user_id, worker_name: wr.worker_name, overtime_hours: wr.overtime_hours || 0, status: wr.status || 'normal', @@ -621,13 +621,13 @@ async function getWorkersForDate(dateStr) { // DailyReports 데이터 추가 dailyReports.forEach(dr => { - if (workerMap.has(dr.worker_id)) { - const worker = workerMap.get(dr.worker_id); + if (workerMap.has(dr.user_id)) { + const worker = workerMap.get(dr.user_id); worker.reported_hours = dr.work_hours; worker.hasDailyReport = true; } else { - workerMap.set(dr.worker_id, { - worker_id: dr.worker_id, + workerMap.set(dr.user_id, { + user_id: dr.user_id, worker_name: dr.worker_name, overtime_hours: 0, status: 'normal', @@ -739,7 +739,7 @@ async function saveEditedWork() { showMessage('수정 중...', 'loading'); - await updateWorkerHours(editingWorker.worker_id, selectedDate, newHours, reason); + await updateWorkerHours(editingWorker.user_id, selectedDate, newHours, reason); showMessage('✅ 근무시간이 성공적으로 수정되었습니다!', 'success'); closeEditModal(); @@ -767,7 +767,7 @@ async function deleteWorker(worker) { try { showMessage('삭제 중...', 'loading'); - await deleteWorkerReport(worker.worker_id, selectedDate); + await deleteWorkerReport(worker.user_id, selectedDate); showMessage('✅ 작업 데이터가 성공적으로 삭제되었습니다!', 'success'); @@ -884,7 +884,7 @@ function renderWorkersList(workers) {
${worker.worker_name}
-
작업자 ID: ${worker.worker_id}
+
작업자 ID: ${worker.user_id}
${getStatusIcon(worker.validationStatus)}
diff --git a/system1-factory/web/js/attendance.js b/system1-factory/web/js/attendance.js index 10ffcc7..99f9941 100644 --- a/system1-factory/web/js/attendance.js +++ b/system1-factory/web/js/attendance.js @@ -45,7 +45,7 @@ async function fetchWorkers() { return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true; }); - workers.sort((a, b) => a.worker_id - b.worker_id); + workers.sort((a, b) => a.user_id - b.user_id); } catch (err) { alert('작업자 불러오기 실패'); } @@ -93,14 +93,14 @@ function renderTable(data, year, month, lastDay) { workers.forEach(w => { // ✅ 월간 데이터 (표에 표시용) const recsThisMonth = data.filter(r => - r.worker_id === w.worker_id && + r.user_id === w.user_id && new Date(r.date).getFullYear() === +year && new Date(r.date).getMonth() + 1 === +month ); // ✅ 연간 데이터 (연차 계산용) const recsThisYear = data.filter(r => - r.worker_id === w.worker_id && + r.user_id === w.user_id && new Date(r.date).getFullYear() === +year ); diff --git a/system1-factory/web/js/daily-issue-api.js b/system1-factory/web/js/daily-issue-api.js index 8a0e95b..ca8dc2d 100644 --- a/system1-factory/web/js/daily-issue-api.js +++ b/system1-factory/web/js/daily-issue-api.js @@ -34,8 +34,8 @@ export async function getWorkersByDate(date) { if (reports && reports.length > 0) { const workerMap = new Map(); reports.forEach(r => { - if (!workerMap.has(r.worker_id)) { - workerMap.set(r.worker_id, { worker_id: r.worker_id, worker_name: r.worker_name }); + if (!workerMap.has(r.user_id)) { + workerMap.set(r.user_id, { user_id: r.user_id, worker_name: r.worker_name }); } }); workers = Array.from(workerMap.values()); diff --git a/system1-factory/web/js/daily-issue-ui.js b/system1-factory/web/js/daily-issue-ui.js index c125ea8..fdc3082 100644 --- a/system1-factory/web/js/daily-issue-ui.js +++ b/system1-factory/web/js/daily-issue-ui.js @@ -54,7 +54,7 @@ export function renderWorkerList(workers) { btn.type = 'button'; btn.className = 'btn'; btn.textContent = worker.worker_name; - btn.dataset.id = worker.worker_id; + btn.dataset.id = worker.user_id; btn.addEventListener('click', () => btn.classList.toggle('selected')); DOM.workerList.appendChild(btn); }); @@ -79,7 +79,7 @@ export function getFormData() { issue_type_id: DOM.issueTypeSelect.value, start_time: DOM.timeStart.value, end_time: DOM.timeEnd.value, - worker_ids: selectedWorkers, // worker_id -> worker_ids 로 명확하게 변경 + user_ids: selectedWorkers, // user_id -> user_ids 로 명확하게 변경 }; for (const key in data) { diff --git a/system1-factory/web/js/daily-work-report-mobile.js b/system1-factory/web/js/daily-work-report-mobile.js index e167209..07a2e1b 100644 --- a/system1-factory/web/js/daily-work-report-mobile.js +++ b/system1-factory/web/js/daily-work-report-mobile.js @@ -792,7 +792,7 @@ const MobileReport = (function() { const reportData = { tbm_assignment_id: tbm.assignment_id, tbm_session_id: tbm.session_id, - worker_id: tbm.worker_id, + user_id: tbm.user_id, project_id: tbm.project_id, work_type_id: tbm.task_id, report_date: reportDate, @@ -895,7 +895,7 @@ const MobileReport = (function() { data: { tbm_assignment_id: tbm.assignment_id, tbm_session_id: tbm.session_id, - worker_id: tbm.worker_id, + user_id: tbm.user_id, project_id: tbm.project_id, work_type_id: tbm.task_id, report_date: reportDate, @@ -974,7 +974,7 @@ const MobileReport = (function() { const state = window.DailyWorkReportState; manualCards[id] = { - worker_id: null, + user_id: null, report_date: getKoreaToday(), project_id: null, work_type_id: null, @@ -1006,9 +1006,9 @@ const MobileReport = (function() {
- - ${workers.map(w => ``).join('')} + ${workers.map(w => ``).join('')}
@@ -1127,7 +1127,7 @@ const MobileReport = (function() { const mc = manualCards[id]; if (!mc) return; - const workerId = mc.worker_id || document.getElementById('m_manual_worker_' + id)?.value; + const workerId = mc.user_id || document.getElementById('m_manual_worker_' + id)?.value; const reportDate = mc.report_date || document.getElementById('m_manual_date_' + id)?.value; const projectId = mc.project_id || document.getElementById('m_manual_project_' + id)?.value; const taskId = mc.task_id || document.getElementById('m_manual_task_' + id)?.value; @@ -1148,7 +1148,7 @@ const MobileReport = (function() { const reportData = { report_date: reportDate, - worker_id: parseInt(workerId), + user_id: parseInt(workerId), work_entries: [{ project_id: parseInt(projectId), task_id: parseInt(taskId), diff --git a/system1-factory/web/js/daily-work-report.js b/system1-factory/web/js/daily-work-report.js index 8bd66ec..ee9e514 100644 --- a/system1-factory/web/js/daily-work-report.js +++ b/system1-factory/web/js/daily-work-report.js @@ -581,7 +581,7 @@ window.submitTbmWorkReport = async function(index) { const reportData = { tbm_assignment_id: tbm.assignment_id, tbm_session_id: tbm.session_id, - worker_id: tbm.worker_id, + user_id: tbm.user_id, project_id: tbm.project_id, work_type_id: tbm.task_id, // task_id를 work_type_id 컬럼에 저장 (직접 작업보고서와 일관성 유지) report_date: reportDate, @@ -729,7 +729,7 @@ window.batchSubmitTbmSession = async function(sessionKey) { data: { tbm_assignment_id: tbm.assignment_id, tbm_session_id: tbm.session_id, - worker_id: tbm.worker_id, + user_id: tbm.user_id, project_id: tbm.project_id, work_type_id: tbm.task_id, // task_id를 work_type_id 컬럼에 저장 (일관성 유지) report_date: reportDate, @@ -847,7 +847,7 @@ window.addManualWorkRow = function() { @@ -1418,7 +1418,7 @@ window.submitManualWorkReport = async function(manualIndex) { // 주의: 서비스에서 task_id를 work_type_id 컬럼에 매핑함 const reportData = { report_date: reportDate, - worker_id: parseInt(workerId), + user_id: parseInt(workerId), work_entries: [{ project_id: parseInt(projectId), task_id: parseInt(taskId), // 서비스에서 work_type_id로 매핑됨 @@ -1577,7 +1577,7 @@ window.submitAllManualWorkReports = async function() { // 서비스 레이어가 기대하는 형식으로 변환 const reportData = { report_date: reportDate, - worker_id: parseInt(workerId), + user_id: parseInt(workerId), work_entries: [{ project_id: parseInt(projectId), task_id: parseInt(taskId), @@ -2282,7 +2282,7 @@ async function loadTbmTeamForDate(date) { // 팀 구성 조회 const teamRes = await window.apiCall(`/tbm/sessions/${targetSession.session_id}/team`); if (teamRes && teamRes.success && teamRes.data) { - const teamWorkerIds = teamRes.data.map(m => m.worker_id); + const teamWorkerIds = teamRes.data.map(m => m.user_id); console.log(`✅ TBM 팀 구성 로드 성공: ${teamWorkerIds.length}명`); return teamWorkerIds; } @@ -2334,16 +2334,16 @@ async function populateWorkerGrid() { btn.type = 'button'; btn.className = 'worker-card'; btn.textContent = worker.worker_name; - btn.dataset.id = worker.worker_id; + btn.dataset.id = worker.user_id; // TBM 팀 구성에 포함된 작업자는 자동 선택 - if (tbmWorkerIds.includes(worker.worker_id)) { + if (tbmWorkerIds.includes(worker.user_id)) { btn.classList.add('selected'); - selectedWorkers.add(worker.worker_id); + selectedWorkers.add(worker.user_id); } btn.addEventListener('click', () => { - toggleWorkerSelection(worker.worker_id, btn); + toggleWorkerSelection(worker.user_id, btn); }); grid.appendChild(btn); @@ -2673,12 +2673,12 @@ async function saveWorkReport() { const failureDetails = []; for (const workerId of selectedWorkers) { - const workerName = workers.find(w => w.worker_id == workerId)?.worker_name || '알 수 없음'; + const workerName = workers.find(w => w.user_id == workerId)?.worker_name || '알 수 없음'; // 서버가 기대하는 work_entries 배열 형태로 전송 const requestData = { report_date: reportDate, - worker_id: parseInt(workerId), + user_id: parseInt(workerId), work_entries: newWorkEntries.map(entry => ({ project_id: entry.project_id, task_id: entry.work_type_id, // 서버에서 task_id로 기대 diff --git a/system1-factory/web/js/daily-work-report/state.js b/system1-factory/web/js/daily-work-report/state.js index 2df535c..889fe94 100644 --- a/system1-factory/web/js/daily-work-report/state.js +++ b/system1-factory/web/js/daily-work-report/state.js @@ -96,7 +96,7 @@ class DailyWorkReportState extends BaseState { */ selectAllWorkers(select = true) { if (select) { - this.workers.forEach(w => this.selectedWorkers.add(w.worker_id)); + this.workers.forEach(w => this.selectedWorkers.add(w.user_id)); } else { this.selectedWorkers.clear(); } diff --git a/system1-factory/web/js/department-management.js b/system1-factory/web/js/department-management.js index e159574..33b645a 100644 --- a/system1-factory/web/js/department-management.js +++ b/system1-factory/web/js/department-management.js @@ -110,11 +110,11 @@ function renderWorkerList(workers) { } container.innerHTML = workers.map(worker => ` -
+
- +
${worker.worker_name.charAt(0)}
${worker.worker_name} diff --git a/system1-factory/web/js/load-sections.js b/system1-factory/web/js/load-sections.js index 6b735ff..da3fd38 100644 --- a/system1-factory/web/js/load-sections.js +++ b/system1-factory/web/js/load-sections.js @@ -23,7 +23,7 @@ async function fetchDashboardStats() { // 필요한 데이터 형태로 가공 (예시) return { today_reports_count: stats.length, - today_workers_count: new Set(stats.map(d => d.worker_id)).size, + today_workers_count: new Set(stats.map(d => d.user_id)).size, }; } catch (error) { console.error('대시보드 통계 데이터 로드 실패:', error); diff --git a/system1-factory/web/js/manage-user.js b/system1-factory/web/js/manage-user.js index 253195e..bd8c97a 100644 --- a/system1-factory/web/js/manage-user.js +++ b/system1-factory/web/js/manage-user.js @@ -147,7 +147,7 @@ userForm?.addEventListener('submit', async e => { password: document.getElementById('password').value.trim(), name: document.getElementById('name').value.trim(), access_level: document.getElementById('access_level').value, - worker_id: document.getElementById('worker_id').value || null + user_id: document.getElementById('user_id').value || null }; try { @@ -206,13 +206,13 @@ async function loadUsers() { list.forEach(item => { item.access_level = accessLabels[item.access_level] || item.access_level; - item.worker_id = item.worker_id || '-'; + item.user_id = item.user_id || '-'; // 행 생성 const tr = document.createElement('tr'); // 데이터 컬럼 - ['user_id', 'username', 'name', 'access_level', 'worker_id'].forEach(key => { + ['user_id', 'username', 'name', 'access_level', 'user_id'].forEach(key => { const td = document.createElement('td'); td.textContent = item[key] || '-'; tr.appendChild(td); @@ -267,7 +267,7 @@ async function loadUsers() { } async function loadWorkerOptions() { - const select = document.getElementById('worker_id'); + const select = document.getElementById('user_id'); if (!select) return; try { @@ -289,8 +289,8 @@ async function loadWorkerOptions() { if (Array.isArray(workers)) { workers.forEach(w => { const opt = document.createElement('option'); - opt.value = w.worker_id; - opt.textContent = `${w.worker_name} (${w.worker_id})`; + opt.value = w.user_id; + opt.textContent = `${w.worker_name} (${w.user_id})`; select.appendChild(opt); }); } diff --git a/system1-factory/web/js/manage-worker.js b/system1-factory/web/js/manage-worker.js index bb44577..269877f 100644 --- a/system1-factory/web/js/manage-worker.js +++ b/system1-factory/web/js/manage-worker.js @@ -80,10 +80,10 @@ async function loadWorkers() { if (Array.isArray(list)) { list.forEach(item => { - const row = createRow(item, ['worker_id', 'worker_name', 'position'], async w => { + const row = createRow(item, ['user_id', 'worker_name', 'position'], async w => { if (!confirm('삭제하시겠습니까?')) return; try { - const delRes = await fetch(`${API}/workers/${w.worker_id}`, { + const delRes = await fetch(`${API}/workers/${w.user_id}`, { method: 'DELETE', headers: getAuthHeaders() }); diff --git a/system1-factory/web/js/management-dashboard.js b/system1-factory/web/js/management-dashboard.js index 36342ed..cfd6eca 100644 --- a/system1-factory/web/js/management-dashboard.js +++ b/system1-factory/web/js/management-dashboard.js @@ -255,11 +255,11 @@ function analyzeDashboardData() { // 작업자별 데이터 그룹화 const workerWorkData = {}; workData.forEach(work => { - const workerId = work.worker_id; - if (!workerWorkData[workerId]) { - workerWorkData[workerId] = []; + const userId = work.user_id; + if (!workerWorkData[userId]) { + workerWorkData[userId] = []; } - workerWorkData[workerId].push(work); + workerWorkData[userId].push(work); }); // 전체 통계 계산 @@ -272,7 +272,7 @@ function analyzeDashboardData() { // 작업자별 상세 분석 (개선된 버전) const workerAnalysis = workers.map(worker => { - const workerWorks = workerWorkData[worker.worker_id] || []; + const workerWorks = workerWorkData[worker.user_id] || []; const workerHours = workerWorks.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0); // 작업 유형 분석 (실제 이름으로) @@ -455,7 +455,7 @@ function createWorkerRow(worker) {
${updateTimeText}
- @@ -479,7 +479,7 @@ function formatDateTime(date) { // 작업자 상세 모달 표시 (안전한 버전) function showWorkerDetailSafe(workerId) { // 현재 분석된 데이터에서 해당 작업자 찾기 - const worker = filteredWorkData.find(w => w.worker_id == workerId); + const worker = filteredWorkData.find(w => w.user_id == workerId); if (!worker) { showMessage('작업자 정보를 찾을 수 없습니다.', 'error'); return; diff --git a/system1-factory/web/js/modern-dashboard.js b/system1-factory/web/js/modern-dashboard.js index ea5be23..51f675e 100644 --- a/system1-factory/web/js/modern-dashboard.js +++ b/system1-factory/web/js/modern-dashboard.js @@ -246,7 +246,7 @@ async function loadWorkData(date) { // ========== 요약 카드 업데이트 ========== // function updateSummaryCards() { // 오늘 작업자 수 - const todayWorkersCount = new Set(workData.map(w => w.worker_id)).size; + const todayWorkersCount = new Set(workData.map(w => w.user_id)).size; updateSummaryCard(elements.todayWorkers, todayWorkersCount, '명'); // 총 작업 시간 @@ -326,7 +326,7 @@ function displayWorkStatus() { // 작업자별 상황 분석 const workerStatusList = allWorkers.map(worker => { - const todayWork = workData.filter(w => w.worker_id === worker.worker_id); + const todayWork = workData.filter(w => w.user_id === worker.user_id); const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); // 휴가/연차 제외한 실제 작업시간 계산 @@ -453,7 +453,7 @@ function displayWorkStatus() { else if (worker.status === 'partial') iconKey = 'partial'; return ` - +
${worker.worker_name.charAt(0)} @@ -492,7 +492,7 @@ function displayWorkStatus() {
- ${worker.vacationType ? ` - ` : ''} ${worker.status === 'overtime-warning' ? ` - '; } else { - btnHtml = ''; + btnHtml = ''; } html += '
' + @@ -1129,7 +1129,7 @@ }; window.startPull = async function(workerId, workerName, maxHours) { - pullWorker = { worker_id: workerId, worker_name: workerName, max_hours: maxHours }; + pullWorker = { user_id: workerId, worker_name: workerName, max_hours: maxHours }; document.getElementById('pullHoursTitle').textContent = esc(workerName) + ' 빼오기'; document.getElementById('pullHoursSubtitle').textContent = '최대 ' + maxHours + 'h 가능'; document.getElementById('pullHoursInput').value = maxHours; @@ -1167,7 +1167,7 @@ var pullWorkTypeId = document.getElementById('pullWorkTypeId').value || null; var res = await window.TbmAPI.transfer({ transfer_type: 'pull', - worker_id: pullWorker.worker_id, + user_id: pullWorker.user_id, source_session_id: pullSessionId, dest_session_id: myDraftSession.session_id, hours: hours, @@ -1230,13 +1230,13 @@ // 현재 세션 리더를 제외한 반장/그룹장 목록 var leaders = workers.filter(function(w) { return (w.job_type === 'leader' || w.job_type === '그룹장' || w.job_type === 'admin') && - w.worker_id !== handoverSession.leader_id; + w.user_id !== handoverSession.leader_user_id; }); var leaderSelect = document.getElementById('handoverLeaderId'); leaderSelect.innerHTML = '' + leaders.map(function(w) { - return ''; + return ''; }).join(''); // 인계할 팀원 체크리스트 @@ -1246,7 +1246,7 @@ } else { listEl.innerHTML = team.map(function(m) { return ''; @@ -1301,13 +1301,13 @@ var handoverData = { session_id: handoverSessionId, - from_leader_id: handoverSession.leader_id, - to_leader_id: toLeaderId, + from_leader_user_id: handoverSession.leader_user_id, + to_leader_user_id: toLeaderId, handover_date: today, handover_time: now, reason: '모바일 인계', handover_notes: notes, - worker_ids: workerIds + user_ids: workerIds }; var res = await window.TbmAPI.saveHandover(handoverData); diff --git a/system1-factory/web/js/tbm.js b/system1-factory/web/js/tbm.js index e6399ab..53f1697 100644 --- a/system1-factory/web/js/tbm.js +++ b/system1-factory/web/js/tbm.js @@ -394,11 +394,11 @@ function openNewTbmModal() { } // 입력자 자동 설정 (readonly) - if (currentUser && currentUser.worker_id) { - const worker = allWorkers.find(w => w.worker_id === currentUser.worker_id); + if (currentUser && currentUser.user_id) { + const worker = allWorkers.find(w => w.user_id === currentUser.user_id); if (worker) { document.getElementById('leaderName').textContent = worker.worker_name; - document.getElementById('leaderId').value = worker.worker_id; + document.getElementById('leaderId').value = worker.user_id; } } else if (currentUser && currentUser.name) { document.getElementById('leaderName').textContent = currentUser.name; @@ -444,7 +444,7 @@ async function renderNewTbmWorkerGrid() { todayAssignmentsMap = {}; assignments.forEach(a => { if (a.sessions && a.sessions.length > 0) { - todayAssignmentsMap[a.worker_id] = a; + todayAssignmentsMap[a.user_id] = a; } }); } catch(e) { @@ -454,8 +454,8 @@ async function renderNewTbmWorkerGrid() { } grid.innerHTML = allWorkers.map(w => { - const checked = selectedWorkersForNewTbm.has(w.worker_id) ? 'checked' : ''; - const assignment = todayAssignmentsMap[w.worker_id]; + const checked = selectedWorkersForNewTbm.has(w.user_id) ? 'checked' : ''; + const assignment = todayAssignmentsMap[w.user_id]; const fullyAssigned = assignment && assignment.total_hours >= 8; const partiallyAssigned = assignment && assignment.total_hours > 0 && assignment.total_hours < 8; @@ -474,9 +474,9 @@ async function renderNewTbmWorkerGrid() { } return ` -