Files
TK-FB-Project/api.hyungi.net/controllers/dailyWorkReportController.js

789 lines
24 KiB
JavaScript

// controllers/dailyWorkReportController.js - 권한별 전체 조회 지원 버전
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
/**
* 📝 작업보고서 생성 (누적 방식 - 덮어쓰기 없음!)
*/
const createDailyWorkReport = (req, res) => {
const { report_date, worker_id, work_entries } = req.body;
const created_by = req.user?.user_id || req.user?.id;
const created_by_name = req.user?.name || req.user?.username || '알 수 없는 사용자';
// 1. 기본 유효성 검사
if (!report_date || !worker_id || !work_entries) {
return res.status(400).json({
error: '필수 필드가 누락되었습니다.',
required: ['report_date', 'worker_id', 'work_entries'],
received: {
report_date: !!report_date,
worker_id: !!worker_id,
work_entries: !!work_entries
}
});
}
if (!Array.isArray(work_entries) || work_entries.length === 0) {
return res.status(400).json({
error: '최소 하나의 작업 항목이 필요합니다.',
received_entries: work_entries?.length || 0
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
// 2. 작업 항목 유효성 검사
for (let i = 0; i < work_entries.length; i++) {
const entry = work_entries[i];
const requiredFields = ['project_id', 'work_type_id', 'work_status_id', 'work_hours'];
for (const field of requiredFields) {
if (entry[field] === undefined || entry[field] === null || entry[field] === '') {
return res.status(400).json({
error: `작업 항목 ${i + 1}${field}가 누락되었습니다.`,
entry_index: i,
missing_field: field
});
}
}
// 에러 상태인 경우 에러 타입 필수
if (entry.work_status_id === 2 && (!entry.error_type_id)) {
return res.status(400).json({
error: `작업 항목 ${i + 1}이 에러 상태인 경우 error_type_id가 필요합니다.`,
entry_index: i
});
}
// 시간 유효성 검사
const hours = parseFloat(entry.work_hours);
if (isNaN(hours) || hours < 0 || hours > 24) {
return res.status(400).json({
error: `작업 항목 ${i + 1}의 작업시간이 유효하지 않습니다. (0-24시간)`,
entry_index: i,
received_hours: entry.work_hours
});
}
}
// 3. 총 시간 계산
const total_hours = work_entries.reduce((sum, entry) => sum + (parseFloat(entry.work_hours) || 0), 0);
// 4. 요청 데이터 구성
const reportData = {
report_date,
worker_id: parseInt(worker_id),
work_entries,
created_by,
created_by_name,
total_hours,
is_update: false
};
console.log('📝 작업보고서 누적 추가 요청:', {
date: report_date,
worker: worker_id,
creator: created_by_name,
creator_id: created_by,
entries: work_entries.length,
total_hours
});
// 5. 누적 추가 실행 (덮어쓰기 없음!)
dailyWorkReportModel.createDailyReport(reportData, (err, result) => {
if (err) {
console.error('작업보고서 생성 오류:', err);
return res.status(500).json({
error: '작업보고서 생성 중 오류가 발생했습니다.',
details: err.message,
timestamp: new Date().toISOString()
});
}
console.log('✅ 작업보고서 누적 추가 성공:', result);
res.status(201).json({
message: '작업보고서가 성공적으로 누적 추가되었습니다.',
report_date,
worker_id,
created_by: created_by_name,
timestamp: new Date().toISOString(),
...result
});
});
};
/**
* 📊 누적 현황 조회 (새로운 기능)
*/
const getAccumulatedReports = (req, res) => {
const { date, worker_id } = req.query;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2024-06-16&worker_id=1'
});
}
console.log(`📊 누적 현황 조회: date=${date}, worker_id=${worker_id}`);
dailyWorkReportModel.getAccumulatedReportsByDate(date, worker_id, (err, data) => {
if (err) {
console.error('누적 현황 조회 오류:', err);
return res.status(500).json({
error: '누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📊 누적 현황 조회 결과: ${data.length}`);
res.json({
date,
worker_id,
total_entries: data.length,
accumulated_data: data,
timestamp: new Date().toISOString()
});
});
};
/**
* 📊 기여자별 요약 조회 (새로운 기능)
*/
const getContributorsSummary = (req, res) => {
const { date, worker_id } = req.query;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2024-06-16&worker_id=1'
});
}
console.log(`📊 기여자별 요약 조회: date=${date}, worker_id=${worker_id}`);
dailyWorkReportModel.getContributorsByDate(date, worker_id, (err, data) => {
if (err) {
console.error('기여자별 요약 조회 오류:', err);
return res.status(500).json({
error: '기여자별 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
const totalHours = data.reduce((sum, contributor) => sum + parseFloat(contributor.total_hours || 0), 0);
console.log(`📊 기여자별 요약: ${data.length}명, 총 ${totalHours}시간`);
res.json({
date,
worker_id,
contributors: data,
total_contributors: data.length,
grand_total_hours: totalHours,
timestamp: new Date().toISOString()
});
});
};
/**
* 📊 개인 누적 현황 조회 (새로운 기능)
*/
const getMyAccumulatedData = (req, res) => {
const { date, worker_id } = req.query;
const created_by = req.user?.user_id || req.user?.id;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2024-06-16&worker_id=1'
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`📊 개인 누적 현황 조회: date=${date}, worker_id=${worker_id}, created_by=${created_by}`);
dailyWorkReportModel.getMyAccumulatedHours(date, worker_id, created_by, (err, data) => {
if (err) {
console.error('개인 누적 현황 조회 오류:', err);
return res.status(500).json({
error: '개인 누적 현황 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📊 개인 누적: ${data.my_entry_count}개 항목, ${data.my_total_hours}시간`);
res.json({
date,
worker_id,
created_by,
my_data: data,
timestamp: new Date().toISOString()
});
});
};
/**
* 🗑️ 개별 항목 삭제 (본인 작성분만 - 새로운 기능)
*/
const removeMyEntry = (req, res) => {
const { id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`🗑️ 개별 항목 삭제 요청: id=${id}, 삭제자=${deleted_by}`);
dailyWorkReportModel.removeSpecificEntry(id, deleted_by, (err, result) => {
if (err) {
console.error('개별 항목 삭제 오류:', err);
return res.status(500).json({
error: '항목 삭제 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`✅ 개별 항목 삭제 완료: id=${id}`);
res.json({
message: '항목이 성공적으로 삭제되었습니다.',
id: id,
deleted_by,
timestamp: new Date().toISOString(),
...result
});
});
};
/**
* 📊 작업보고서 조회 (권한별 전체 조회 지원 - 핵심 수정!)
*/
const getDailyWorkReports = (req, res) => {
const { date, worker_id, created_by: requested_created_by, view_all, admin, all, no_filter, ignore_created_by } = req.query;
const current_user_id = req.user?.user_id || req.user?.id;
const user_access_level = req.user?.access_level;
if (!current_user_id) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
// 🎯 권한별 필터링 로직 개선
const isAdmin = user_access_level === 'system' || user_access_level === 'admin';
const hasViewAllFlag = view_all === 'true' || admin === 'true' || all === 'true' ||
no_filter === 'true' || ignore_created_by === 'true' ||
requested_created_by === 'all' || requested_created_by === '';
const canViewAll = isAdmin || hasViewAllFlag;
// 관리자가 아니고 전체 조회 플래그도 없으면 본인 작성분으로 제한
let final_created_by = null;
if (!canViewAll) {
final_created_by = requested_created_by || current_user_id;
} else if (requested_created_by && requested_created_by !== 'all' && requested_created_by !== '') {
final_created_by = requested_created_by;
}
console.log('📊 작업보고서 조회 요청:', {
date,
worker_id,
requested_created_by,
current_user_id,
user_access_level,
isAdmin,
hasViewAllFlag,
canViewAll,
final_created_by
});
if (date && final_created_by) {
// 날짜 + 작성자별 조회
dailyWorkReportModel.getByDateAndCreator(date, final_created_by, (err, data) => {
if (err) {
console.error('작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📊 날짜+작성자별 조회 결과: ${data.length}`);
res.json(data);
});
} else if (date && worker_id) {
// 날짜 + 작업자별 조회
dailyWorkReportModel.getByDateAndWorker(date, worker_id, (err, data) => {
if (err) {
console.error('작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
// 🎯 권한별 필터링
let finalData = data;
if (!canViewAll) {
finalData = data.filter(report => report.created_by === current_user_id);
console.log(`📊 권한 필터링: 전체 ${data.length}개 → ${finalData.length}`);
} else {
console.log(`📊 관리자/전체 조회 권한: ${data.length}개 전체 반환`);
}
res.json(finalData);
});
} else if (date) {
// 날짜별 조회
dailyWorkReportModel.getByDate(date, (err, data) => {
if (err) {
console.error('작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
// 🎯 권한별 필터링
let finalData = data;
if (!canViewAll) {
finalData = data.filter(report => report.created_by === current_user_id);
console.log(`📊 권한 필터링: 전체 ${data.length}개 → ${finalData.length}`);
} else {
console.log(`📊 관리자/전체 조회 권한: ${data.length}개 전체 반환`);
}
res.json(finalData);
});
} else {
res.status(400).json({
error: '날짜(date) 파라미터가 필요합니다.',
example: 'date=2024-06-16',
optional: ['worker_id', 'created_by', 'view_all', 'admin', 'all']
});
}
};
/**
* 📊 날짜별 작업보고서 조회 (경로 파라미터 - 권한별 전체 조회 지원)
*/
const getDailyWorkReportsByDate = (req, res) => {
const { date } = req.params;
const current_user_id = req.user?.user_id || req.user?.id;
const user_access_level = req.user?.access_level;
if (!current_user_id) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
const isAdmin = user_access_level === 'system' || user_access_level === 'admin';
console.log(`📊 날짜별 조회 (경로): date=${date}, user=${current_user_id}, 권한=${user_access_level}, 관리자=${isAdmin}`);
dailyWorkReportModel.getByDate(date, (err, data) => {
if (err) {
console.error('날짜별 작업보고서 조회 오류:', err);
return res.status(500).json({
error: '작업보고서 조회 중 오류가 발생했습니다.',
details: err.message
});
}
// 🎯 권한별 필터링
let finalData = data;
if (!isAdmin) {
finalData = data.filter(report => report.created_by === current_user_id);
console.log(`📊 권한 필터링: 전체 ${data.length}개 → ${finalData.length}`);
} else {
console.log(`📊 관리자 권한으로 전체 조회: ${data.length}`);
}
res.json(finalData);
});
};
/**
* 🔍 작업보고서 검색 (페이지네이션 포함)
*/
const searchWorkReports = (req, res) => {
const { start_date, end_date, worker_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']
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
const searchParams = {
start_date,
end_date,
worker_id: worker_id ? parseInt(worker_id) : null,
project_id: project_id ? parseInt(project_id) : null,
work_status_id: work_status_id ? parseInt(work_status_id) : null,
created_by, // 작성자 필터링 추가
page: parseInt(page),
limit: parseInt(limit)
};
console.log('🔍 작업보고서 검색 요청:', searchParams);
dailyWorkReportModel.searchWithDetails(searchParams, (err, data) => {
if (err) {
console.error('작업보고서 검색 오류:', err);
return res.status(500).json({
error: '작업보고서 검색 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`🔍 검색 결과: ${data.reports?.length || 0}개 (전체: ${data.total || 0}개)`);
res.json(data);
});
};
/**
* 📈 통계 조회 (작성자별 필터링)
*/
const getWorkReportStats = (req, res) => {
const { start_date, end_date } = 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'
});
}
if (!created_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`📈 통계 조회: ${start_date} ~ ${end_date}, 요청자: ${created_by}`);
dailyWorkReportModel.getStatistics(start_date, end_date, (err, data) => {
if (err) {
console.error('통계 조회 오류:', err);
return res.status(500).json({
error: '통계 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json({
...data,
metadata: {
note: '현재는 전체 통계입니다. 개인별 통계는 추후 구현 예정',
requested_by: created_by,
period: `${start_date} ~ ${end_date}`,
timestamp: new Date().toISOString()
}
});
});
};
/**
* 📊 일일 근무 요약 조회
*/
const getDailySummary = (req, res) => {
const { date, worker_id } = req.query;
if (date) {
console.log(`📊 일일 요약 조회: date=${date}`);
dailyWorkReportModel.getSummaryByDate(date, (err, data) => {
if (err) {
console.error('일일 요약 조회 오류:', err);
return res.status(500).json({
error: '일일 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json(data);
});
} else if (worker_id) {
console.log(`📊 작업자별 요약 조회: worker_id=${worker_id}`);
dailyWorkReportModel.getSummaryByWorker(worker_id, (err, data) => {
if (err) {
console.error('작업자별 요약 조회 오류:', err);
return res.status(500).json({
error: '작업자별 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json(data);
});
} else {
res.status(400).json({
error: 'date 또는 worker_id 파라미터가 필요합니다.',
examples: [
'date=2024-06-16',
'worker_id=1'
]
});
}
};
/**
* 📅 월간 요약 조회
*/
const getMonthlySummary = (req, res) => {
const { year, month } = req.query;
if (!year || !month) {
return res.status(400).json({
error: 'year와 month가 필요합니다.',
example: 'year=2024&month=01',
note: 'month는 01, 02, ..., 12 형식으로 입력하세요.'
});
}
console.log(`📅 월간 요약 조회: ${year}-${month}`);
dailyWorkReportModel.getMonthlySummary(year, month, (err, data) => {
if (err) {
console.error('월간 요약 조회 오류:', err);
return res.status(500).json({
error: '월간 요약 조회 중 오류가 발생했습니다.',
details: err.message
});
}
res.json({
year: parseInt(year),
month: parseInt(month),
summary: data,
total_entries: data.length,
timestamp: new Date().toISOString()
});
});
};
/**
* ✏️ 작업보고서 수정
*/
const updateWorkReport = (req, res) => {
const { id } = req.params;
const updateData = req.body;
const updated_by = req.user?.user_id || req.user?.id;
if (!updated_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
updateData.updated_by = updated_by;
console.log(`✏️ 작업보고서 수정 요청: id=${id}, 수정자=${updated_by}`);
dailyWorkReportModel.updateById(id, updateData, (err, affectedRows) => {
if (err) {
console.error('작업보고서 수정 오류:', err);
return res.status(500).json({
error: '작업보고서 수정 중 오류가 발생했습니다.',
details: err.message
});
}
if (affectedRows === 0) {
return res.status(404).json({
error: '수정할 작업보고서를 찾을 수 없습니다.',
id: id
});
}
console.log(`✅ 작업보고서 수정 완료: id=${id}`);
res.json({
message: '작업보고서가 성공적으로 수정되었습니다.',
id: id,
affected_rows: affectedRows,
updated_by,
timestamp: new Date().toISOString()
});
});
};
/**
* 🗑️ 특정 작업보고서 삭제
*/
const removeDailyWorkReport = (req, res) => {
const { id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`🗑️ 작업보고서 삭제 요청: id=${id}, 삭제자=${deleted_by}`);
dailyWorkReportModel.removeById(id, deleted_by, (err, affectedRows) => {
if (err) {
console.error('작업보고서 삭제 오류:', err);
return res.status(500).json({
error: '작업보고서 삭제 중 오류가 발생했습니다.',
details: err.message
});
}
if (affectedRows === 0) {
return res.status(404).json({
error: '삭제할 작업보고서를 찾을 수 없습니다.',
id: id
});
}
console.log(`✅ 작업보고서 삭제 완료: id=${id}`);
res.json({
message: '작업보고서가 성공적으로 삭제되었습니다.',
id: id,
affected_rows: affectedRows,
deleted_by,
timestamp: new Date().toISOString()
});
});
};
/**
* 🗑️ 작업자의 특정 날짜 전체 삭제
*/
const removeDailyWorkReportByDateAndWorker = (req, res) => {
const { date, worker_id } = req.params;
const deleted_by = req.user?.user_id || req.user?.id;
if (!deleted_by) {
return res.status(401).json({
error: '사용자 인증 정보가 없습니다.'
});
}
console.log(`🗑️ 날짜+작업자별 전체 삭제 요청: date=${date}, worker_id=${worker_id}, 삭제자=${deleted_by}`);
dailyWorkReportModel.removeByDateAndWorker(date, worker_id, deleted_by, (err, affectedRows) => {
if (err) {
console.error('작업보고서 전체 삭제 오류:', err);
return res.status(500).json({
error: '작업보고서 삭제 중 오류가 발생했습니다.',
details: err.message
});
}
if (affectedRows === 0) {
return res.status(404).json({
error: '삭제할 작업보고서를 찾을 수 없습니다.',
date: date,
worker_id: worker_id
});
}
console.log(`✅ 날짜+작업자별 전체 삭제 완료: ${affectedRows}`);
res.json({
message: `${date} 날짜의 작업자 ${worker_id} 작업보고서 ${affectedRows}개가 삭제되었습니다.`,
date,
worker_id,
affected_rows: affectedRows,
deleted_by,
timestamp: new Date().toISOString()
});
});
};
/**
* 📋 마스터 데이터 조회 함수들
*/
const getWorkTypes = (req, res) => {
console.log('📋 작업 유형 조회 요청');
dailyWorkReportModel.getAllWorkTypes((err, data) => {
if (err) {
console.error('작업 유형 조회 오류:', err);
return res.status(500).json({
error: '작업 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 작업 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
const getWorkStatusTypes = (req, res) => {
console.log('📋 업무 상태 유형 조회 요청');
dailyWorkReportModel.getAllWorkStatusTypes((err, data) => {
if (err) {
console.error('업무 상태 유형 조회 오류:', err);
return res.status(500).json({
error: '업무 상태 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 업무 상태 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
const getErrorTypes = (req, res) => {
console.log('📋 에러 유형 조회 요청');
dailyWorkReportModel.getAllErrorTypes((err, data) => {
if (err) {
console.error('에러 유형 조회 오류:', err);
return res.status(500).json({
error: '에러 유형 조회 중 오류가 발생했습니다.',
details: err.message
});
}
console.log(`📋 에러 유형 조회 결과: ${data.length}`);
res.json(data);
});
};
// 모든 컨트롤러 함수 내보내기 (권한별 조회 지원)
module.exports = {
// 📝 핵심 CRUD 함수들 (권한별 전체 조회 지원)
createDailyWorkReport, // 누적 추가 (덮어쓰기 없음)
getDailyWorkReports, // 조회 (권한별 필터링 개선)
getDailyWorkReportsByDate, // 날짜별 조회 (권한별 필터링 개선)
searchWorkReports, // 검색 (페이지네이션)
updateWorkReport, // 수정
removeDailyWorkReport, // 개별 삭제
removeDailyWorkReportByDateAndWorker, // 전체 삭제
// 🔄 누적 관련 새로운 함수들
getAccumulatedReports, // 누적 현황 조회
getContributorsSummary, // 기여자별 요약
getMyAccumulatedData, // 개인 누적 현황
removeMyEntry, // 개별 항목 삭제 (본인 것만)
// 📊 요약 및 통계 함수들
getDailySummary, // 일일 요약
getMonthlySummary, // 월간 요약
getWorkReportStats, // 통계
// 📋 마스터 데이터 함수들
getWorkTypes, // 작업 유형 목록
getWorkStatusTypes, // 업무 상태 유형 목록
getErrorTypes // 에러 유형 목록
};