feat: 권한 탭 분리 + 부서 인원 표시 + 다수 시스템 개선

- tkuser: 권한 관리를 별도 탭으로 분리, 부서 클릭 시 소속 인원 목록 표시
- system1: 모바일 UI 개선, nginx 권한 보정, 신고 카테고리 타입 마이그레이션
- system2: 신고 상세/보고서 개선, 내 보고서 페이지 추가
- system3: 이슈 뷰/수신함/관리함 개선
- gateway: 포털 라우팅 수정
- user-management API: 부서별 권한 벌크 설정 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-23 14:12:57 +09:00
parent bf4000c4ae
commit 3cc29c03a8
37 changed files with 1751 additions and 233 deletions

View File

@@ -272,6 +272,20 @@ exports.createReport = async (req, res) => {
}
}
// category_type 조회
let categoryType = null;
try {
const catInfo = await new Promise((resolve, reject) => {
workIssueModel.getCategoryById(issue_category_id, (catErr, data) => {
if (catErr) reject(catErr); else resolve(data);
});
});
if (catInfo) categoryType = catInfo.category_type;
} catch (catErr) {
console.error('카테고리 조회 실패:', catErr);
return res.status(500).json({ success: false, error: '카테고리 정보 조회 실패' });
}
const reportData = {
reporter_id,
factory_category_id: factory_category_id || null,
@@ -282,6 +296,7 @@ exports.createReport = async (req, res) => {
visit_request_id: visit_request_id || null,
issue_category_id,
issue_item_id: finalItemId || null,
category_type: categoryType,
additional_description: additional_description || null,
...photoPaths
};
@@ -326,6 +341,26 @@ exports.createReport = async (req, res) => {
const descText = additional_description || categoryInfo.category_name;
// 위치 정보 조회
let locationInfo = custom_location || null;
if (factory_category_id) {
try {
const locationParts = await new Promise((resolve, reject) => {
workIssueModel.getLocationNames(factory_category_id, workplace_id, (locErr, data) => {
if (locErr) reject(locErr); else resolve(data);
});
});
if (locationParts) {
locationInfo = locationParts.factory_name || '';
if (locationParts.workplace_name) {
locationInfo += ` - ${locationParts.workplace_name}`;
}
}
} catch (locErr) {
console.error('위치 정보 조회 실패:', locErr.message);
}
}
// 원래 신고자의 SSO 토큰 추출
const originalToken = (req.headers['authorization'] || '').replace('Bearer ', '');
@@ -335,6 +370,7 @@ exports.createReport = async (req, res) => {
reporter_name: req.user.name || req.user.username,
tk_issue_id: reportId,
project_id: project_id || null,
location_info: locationInfo,
photos: photoBase64List,
ssoToken: originalToken
});
@@ -667,6 +703,30 @@ exports.getStatusLogs = (req, res) => {
});
};
// ==================== 유형 이관 ====================
/**
* 신고 유형 이관
*/
exports.transferReport = (req, res) => {
const { id } = req.params;
const { category_type } = req.body;
// ENUM 유효성 검증
const validTypes = ['nonconformity', 'safety', 'facility'];
if (!category_type || !validTypes.includes(category_type)) {
return res.status(400).json({ success: false, error: '유효하지 않은 유형입니다. (nonconformity, safety, facility)' });
}
workIssueModel.transferCategoryType(id, category_type, req.user.user_id, (err, result) => {
if (err) {
console.error('유형 이관 실패:', err);
return res.status(400).json({ success: false, error: err.message || '유형 이관 실패' });
}
res.json({ success: true, message: '유형이 이관되었습니다.' });
});
};
// ==================== 통계 ====================
/**
@@ -674,6 +734,7 @@ exports.getStatusLogs = (req, res) => {
*/
exports.getStatsSummary = (req, res) => {
const filters = {
category_type: req.query.category_type,
start_date: req.query.start_date,
end_date: req.query.end_date,
factory_category_id: req.query.factory_category_id