feat: TBM 모바일 시스템 + 작업 분할/이동 + 권한 통합
TBM 시스템: - 4단계 워크플로우 (draft→세부편집→완료→작업보고) - 모바일 전용 TBM 페이지 (tbm-mobile.html) + 3단계 생성 위자드 - 작업자 작업 분할 (work_hours + split_seq) - 작업자 이동 보내기/빼오기 (tbm_transfers 테이블) - 생성 시 중복 배정 방지 (당일 배정 현황 조회) - 데스크탑 TBM 페이지 세부편집 기능 추가 작업보고서: - 모바일 전용 작업보고서 페이지 (report-create-mobile.html) - TBM에서 사전 등록된 work_hours 자동 반영 권한 시스템: - tkuser user_page_permissions 테이블과 system1 페이지 접근 연동 - pageAccessRoutes를 userRoutes보다 먼저 등록 (라우트 우선순위 수정) - TKUSER_DEFAULT_ACCESS 폴백 추가 (개인→부서→기본값 3단계) - 권한 캐시키 갱신 (userPageAccess_v2) 기타: - app-init.js 캐시 버스팅 (v=5) - iOS Safari touch-action: manipulation 적용 - KST 타임존 날짜 버그 수정 (toISOString UTC 이슈) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// controllers/tbmController.js - TBM 시스템 컨트롤러
|
||||
const TbmModel = require('../models/tbmModel');
|
||||
const TbmTransferModel = require('../models/tbmTransferModel');
|
||||
|
||||
const TbmController = {
|
||||
// ==================== TBM 세션 관련 ====================
|
||||
@@ -151,7 +152,37 @@ const TbmController = {
|
||||
completeSession: (req, res) => {
|
||||
const { sessionId } = req.params;
|
||||
const endTime = req.body.end_time || new Date().toTimeString().slice(0, 8);
|
||||
const attendanceData = req.body.attendance_data;
|
||||
|
||||
// 근태 데이터가 있으면 새 메서드 사용
|
||||
if (attendanceData && Array.isArray(attendanceData) && attendanceData.length > 0) {
|
||||
const createdBy = req.user.user_id;
|
||||
TbmModel.completeSessionWithAttendance(sessionId, endTime, attendanceData, createdBy, (err, result) => {
|
||||
if (err) {
|
||||
console.error('TBM 세션 완료 처리 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'TBM 세션 완료 처리 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'TBM 세션을 찾을 수 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'TBM 세션이 완료되었습니다.'
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 방식 (하위 호환)
|
||||
TbmModel.completeSession(sessionId, endTime, (err, result) => {
|
||||
if (err) {
|
||||
console.error('TBM 세션 완료 처리 오류:', err);
|
||||
@@ -223,7 +254,8 @@ const TbmController = {
|
||||
work_type_id: req.body.work_type_id || null,
|
||||
task_id: req.body.task_id || null,
|
||||
workplace_category_id: req.body.workplace_category_id || null,
|
||||
workplace_id: req.body.workplace_id || null
|
||||
workplace_id: req.body.workplace_id || null,
|
||||
work_hours: req.body.work_hours !== undefined ? req.body.work_hours : undefined
|
||||
};
|
||||
|
||||
if (!assignmentData.worker_id) {
|
||||
@@ -250,6 +282,34 @@ const TbmController = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 분할 항목 추가 (같은 작업자의 추가 배정)
|
||||
*/
|
||||
addSplitAssignment: (req, res) => {
|
||||
const assignmentData = {
|
||||
session_id: req.params.sessionId,
|
||||
worker_id: req.body.worker_id,
|
||||
work_hours: req.body.work_hours,
|
||||
project_id: req.body.project_id || null,
|
||||
work_type_id: req.body.work_type_id || null,
|
||||
task_id: req.body.task_id || null,
|
||||
workplace_category_id: req.body.workplace_category_id || null,
|
||||
workplace_id: req.body.workplace_id || null
|
||||
};
|
||||
|
||||
if (!assignmentData.worker_id || !assignmentData.work_hours) {
|
||||
return res.status(400).json({ success: false, message: '작업자 ID와 작업시간이 필요합니다.' });
|
||||
}
|
||||
|
||||
TbmModel.addSplitAssignment(assignmentData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('분할 항목 추가 오류:', err);
|
||||
return res.status(500).json({ success: false, message: '분할 항목 추가 중 오류가 발생했습니다.' });
|
||||
}
|
||||
res.json({ success: true, data: result });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 팀 구성 일괄 추가
|
||||
*/
|
||||
@@ -892,6 +952,122 @@ const TbmController = {
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 작업자 이동 관련 ====================
|
||||
|
||||
/**
|
||||
* 이동 실행 (보내기/빼오기)
|
||||
*/
|
||||
createTransfer: (req, res) => {
|
||||
const { transfer_type, worker_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) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '필수 정보가 누락되었습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
const transferDate = today.getFullYear() + '-' +
|
||||
String(today.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(today.getDate()).padStart(2, '0');
|
||||
|
||||
const transferData = {
|
||||
transfer_type, worker_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
|
||||
};
|
||||
|
||||
TbmTransferModel.createTransfer(transferData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('이동 실행 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '이동 실행 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '이동이 완료되었습니다.',
|
||||
data: result
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 이동 내역 조회
|
||||
*/
|
||||
getTransfersByDate: (req, res) => {
|
||||
const { date } = req.params;
|
||||
|
||||
TbmTransferModel.getTransfersByDate(date, (err, results) => {
|
||||
if (err) {
|
||||
console.error('이동 내역 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '이동 내역 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: results });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 이동 취소 (원복)
|
||||
*/
|
||||
cancelTransfer: (req, res) => {
|
||||
const { transferId } = req.params;
|
||||
|
||||
TbmTransferModel.cancelTransfer(transferId, (err, result) => {
|
||||
if (err) {
|
||||
console.error('이동 취소 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '이동 취소 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '이동이 취소되었습니다.'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 당일 전 작업자 배정 현황
|
||||
*/
|
||||
getWorkerAssignmentsByDate: (req, res) => {
|
||||
const { date } = req.params;
|
||||
|
||||
TbmTransferModel.getWorkerAssignmentsByDate(date, (err, results) => {
|
||||
if (err) {
|
||||
console.error('배정 현황 조회 오류:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '배정 현황 조회 중 오류가 발생했습니다.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: results });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업보고서가 작성되지 않은 TBM 팀 배정 조회
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user