refactor: worker_id → user_id 전체 마이그레이션 (Phase 1-4)
sso_users.user_id를 단일 식별자로 통합. JWT에서 worker_id 제거, department_id/is_production 추가. 백엔드 15개 모델, 11개 컨트롤러, 4개 서비스, 7개 라우트, 프론트엔드 32+ JS/11+ HTML 변환. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: '권한이 없습니다' });
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
`;
|
||||
|
||||
|
||||
@@ -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입니다');
|
||||
|
||||
Reference in New Issue
Block a user