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

@@ -237,6 +237,7 @@ const createReport = async (reportData, callback) => {
visit_request_id = null,
issue_category_id,
issue_item_id = null,
category_type,
additional_description = null,
photo_path1 = null,
photo_path2 = null,
@@ -251,11 +252,11 @@ const createReport = async (reportData, callback) => {
const [result] = await db.query(
`INSERT INTO work_issue_reports
(reporter_id, report_date, factory_category_id, workplace_id, project_id, custom_location,
tbm_session_id, visit_request_id, issue_category_id, issue_item_id,
tbm_session_id, visit_request_id, issue_category_id, issue_item_id, category_type,
additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[reporter_id, reportDate, factory_category_id, workplace_id, project_id, custom_location,
tbm_session_id, visit_request_id, issue_category_id, issue_item_id,
tbm_session_id, visit_request_id, issue_category_id, issue_item_id, category_type,
additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5]
);
@@ -291,7 +292,7 @@ const getAllReports = async (filters = {}, callback) => {
u.username as reporter_name, u.name as reporter_full_name,
wc.category_name as factory_name,
w.workplace_name,
irc.category_type, irc.category_name as issue_category_name,
wir.category_type, irc.category_type as original_category_type, irc.category_name as issue_category_name,
iri.item_name as issue_item_name, iri.severity,
assignee.username as assigned_user_name, assignee.name as assigned_full_name
FROM work_issue_reports wir
@@ -313,7 +314,7 @@ const getAllReports = async (filters = {}, callback) => {
}
if (filters.category_type) {
query += ` AND irc.category_type = ?`;
query += ` AND wir.category_type = ?`;
params.push(filters.category_type);
}
@@ -394,7 +395,7 @@ const getReportById = async (reportId, callback) => {
u.username as reporter_name, u.name as reporter_full_name,
wc.category_name as factory_name,
w.workplace_name,
irc.category_type, irc.category_name as issue_category_name,
wir.category_type, irc.category_type as original_category_type, irc.category_name as issue_category_name,
iri.item_name as issue_item_name, iri.severity,
assignee.username as assigned_user_name, assignee.name as assigned_full_name,
assigner.username as assigned_by_name,
@@ -783,25 +784,30 @@ const getStatsSummary = async (filters = {}, callback) => {
let whereClause = '1=1';
const params = [];
if (filters.category_type) {
whereClause += ` AND wir.category_type = ?`;
params.push(filters.category_type);
}
if (filters.start_date && filters.end_date) {
whereClause += ` AND DATE(report_date) BETWEEN ? AND ?`;
whereClause += ` AND DATE(wir.report_date) BETWEEN ? AND ?`;
params.push(filters.start_date, filters.end_date);
}
if (filters.factory_category_id) {
whereClause += ` AND factory_category_id = ?`;
whereClause += ` AND wir.factory_category_id = ?`;
params.push(filters.factory_category_id);
}
const [rows] = await db.query(
`SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'reported' THEN 1 ELSE 0 END) as reported,
SUM(CASE WHEN status = 'received' THEN 1 ELSE 0 END) as received,
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed
FROM work_issue_reports
SUM(CASE WHEN wir.status = 'reported' THEN 1 ELSE 0 END) as reported,
SUM(CASE WHEN wir.status = 'received' THEN 1 ELSE 0 END) as received,
SUM(CASE WHEN wir.status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
SUM(CASE WHEN wir.status = 'completed' THEN 1 ELSE 0 END) as completed,
SUM(CASE WHEN wir.status = 'closed' THEN 1 ELSE 0 END) as closed
FROM work_issue_reports wir
WHERE ${whereClause}`,
params
);
@@ -885,6 +891,86 @@ const getStatsByWorkplace = async (filters = {}, callback) => {
}
};
/**
* 유형 이관 (category_type 변경)
*/
const transferCategoryType = async (reportId, newCategoryType, userId, callback) => {
try {
const db = await getDb();
// 기존 데이터 조회
const [existing] = await db.query(
`SELECT report_id, category_type, modification_history FROM work_issue_reports WHERE report_id = ?`,
[reportId]
);
if (existing.length === 0) {
return callback(new Error('신고를 찾을 수 없습니다.'));
}
const current = existing[0];
const oldCategoryType = current.category_type;
if (oldCategoryType === newCategoryType) {
return callback(new Error('현재 유형과 동일합니다.'));
}
// 수정 이력 추가
const existingHistory = current.modification_history ? JSON.parse(current.modification_history) : [];
const now = new Date().toISOString();
existingHistory.push({
field: 'category_type',
old_value: oldCategoryType,
new_value: newCategoryType,
modified_at: now,
modified_by: userId
});
// category_type 업데이트
const [result] = await db.query(
`UPDATE work_issue_reports
SET category_type = ?, modification_history = ?, updated_at = NOW()
WHERE report_id = ?`,
[newCategoryType, JSON.stringify(existingHistory), reportId]
);
callback(null, result);
} catch (err) {
callback(err);
}
};
/**
* 공장/작업장 이름 조회 (System 3 연동용)
*/
const getLocationNames = async (factoryCategoryId, workplaceId, callback) => {
try {
const db = await getDb();
let factoryName = null;
let workplaceName = null;
if (factoryCategoryId) {
const [rows] = await db.query(
`SELECT category_name FROM workplace_categories WHERE category_id = ?`,
[factoryCategoryId]
);
if (rows.length > 0) factoryName = rows[0].category_name;
}
if (workplaceId) {
const [rows] = await db.query(
`SELECT workplace_name FROM workplaces WHERE workplace_id = ?`,
[workplaceId]
);
if (rows.length > 0) workplaceName = rows[0].workplace_name;
}
callback(null, { factory_name: factoryName, workplace_name: workplaceName });
} catch (err) {
callback(err);
}
};
module.exports = {
// 카테고리
getAllCategories,
@@ -910,6 +996,10 @@ module.exports = {
// System 3 연동
updateMProjectId,
getLocationNames,
// 유형 이관
transferCategoryType,
// 상태 관리
receiveReport,