feat: 모바일 신고 시스템 구축 + tkqc 연동 + tkuser 이슈유형 관리
- tkreport 모바일 신고 페이지 (5단계 위자드: 유형→위치→프로젝트→항목→사진) - 프로젝트 DB 연동 (아코디언 UI: TBM등록/활성프로젝트/모름) - 클라이언트 이미지 리사이징 (1280px, JPEG 80%) - nginx client_max_body_size 50m, /api/projects/ 프록시 추가 - 부적합 신고 → tkqc 자동 연동 (사진 base64 전달, SSO 토큰 유지) - work_issue_reports에 project_id 컬럼 추가 - imageUploadService 경로 수정 (public/uploads → uploads, Docker 볼륨 일치) - tkuser 이슈유형 탭, 휴가관리, nginx 프록시 업데이트 - tkqc 대시보드/수신함/관리함/폐기함 UI 업데이트 - system1 랜딩페이지 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -208,6 +208,7 @@ exports.createReport = async (req, res) => {
|
||||
issue_category_id,
|
||||
issue_item_id,
|
||||
custom_item_name, // 직접 입력한 항목명
|
||||
project_id,
|
||||
additional_description,
|
||||
photos = []
|
||||
} = req.body;
|
||||
@@ -275,6 +276,7 @@ exports.createReport = async (req, res) => {
|
||||
reporter_id,
|
||||
factory_category_id: factory_category_id || null,
|
||||
workplace_id: workplace_id || null,
|
||||
project_id: project_id || null,
|
||||
custom_location: custom_location || null,
|
||||
tbm_session_id: tbm_session_id || null,
|
||||
visit_request_id: visit_request_id || null,
|
||||
@@ -306,23 +308,35 @@ exports.createReport = async (req, res) => {
|
||||
});
|
||||
|
||||
if (categoryInfo && categoryInfo.category_type === 'nonconformity') {
|
||||
// 사진은 System 2에만 저장, URL 참조만 전달
|
||||
const baseUrl = process.env.SYSTEM2_PUBLIC_URL || 'https://tkreport.technicalkorea.net';
|
||||
const photoUrls = Object.values(photoPaths).filter(Boolean)
|
||||
.map(p => `${baseUrl}/api/uploads/${p}`);
|
||||
|
||||
const descParts = [additional_description || categoryInfo.category_name];
|
||||
if (photoUrls.length > 0) {
|
||||
descParts.push('', '[첨부 사진]');
|
||||
photoUrls.forEach((url, i) => descParts.push(`${i + 1}. ${url}`));
|
||||
// 저장된 사진 파일을 base64로 읽어서 System 3에 전달
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const photoBase64List = [];
|
||||
for (const p of Object.values(photoPaths)) {
|
||||
if (!p) continue;
|
||||
try {
|
||||
const filePath = path.join(__dirname, '..', p);
|
||||
const buf = await fs.readFile(filePath);
|
||||
const b64 = `data:image/jpeg;base64,${buf.toString('base64')}`;
|
||||
photoBase64List.push(b64);
|
||||
} catch (readErr) {
|
||||
console.error('사진 파일 읽기 실패:', p, readErr.message);
|
||||
}
|
||||
}
|
||||
|
||||
const descText = additional_description || categoryInfo.category_name;
|
||||
|
||||
// 원래 신고자의 SSO 토큰 추출
|
||||
const originalToken = (req.headers['authorization'] || '').replace('Bearer ', '');
|
||||
|
||||
const result = await mProjectService.sendToMProject({
|
||||
category: categoryInfo.category_name,
|
||||
description: descParts.join('\n'),
|
||||
description: descText,
|
||||
reporter_name: req.user.name || req.user.username,
|
||||
tk_issue_id: reportId,
|
||||
photos: [] // 사진 복사 안 함 (URL 참조만)
|
||||
project_id: project_id || null,
|
||||
photos: photoBase64List,
|
||||
ssoToken: originalToken
|
||||
});
|
||||
if (result.success && result.mProjectId) {
|
||||
workIssueModel.updateMProjectId(reportId, result.mProjectId, () => {});
|
||||
|
||||
@@ -231,6 +231,7 @@ const createReport = async (reportData, callback) => {
|
||||
reporter_id,
|
||||
factory_category_id = null,
|
||||
workplace_id = null,
|
||||
project_id = null,
|
||||
custom_location = null,
|
||||
tbm_session_id = null,
|
||||
visit_request_id = null,
|
||||
@@ -249,11 +250,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, custom_location,
|
||||
(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,
|
||||
additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[reporter_id, reportDate, factory_category_id, workplace_id, custom_location,
|
||||
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,
|
||||
additional_description, photo_path1, photo_path2, photo_path3, photo_path4, photo_path5]
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@ try {
|
||||
|
||||
// 업로드 디렉토리 설정
|
||||
const UPLOAD_DIRS = {
|
||||
issues: path.join(__dirname, '../public/uploads/issues'),
|
||||
equipments: path.join(__dirname, '../public/uploads/equipments')
|
||||
issues: path.join(__dirname, '../uploads/issues'),
|
||||
equipments: path.join(__dirname, '../uploads/equipments')
|
||||
};
|
||||
const UPLOAD_DIR = UPLOAD_DIRS.issues; // 기존 호환성 유지
|
||||
const MAX_SIZE = { width: 1920, height: 1920 };
|
||||
|
||||
@@ -184,14 +184,21 @@ async function sendToMProject(issueData) {
|
||||
description,
|
||||
reporter_name,
|
||||
project_name,
|
||||
project_id = null,
|
||||
tk_issue_id,
|
||||
photos = [],
|
||||
ssoToken = null,
|
||||
} = issueData;
|
||||
|
||||
logger.info('M-Project 연동 시작', { tk_issue_id, category });
|
||||
|
||||
// 인증 토큰 획득
|
||||
const token = await getAuthToken();
|
||||
// SSO 토큰이 있으면 원래 사용자로 전송, 없으면 api_service 토큰
|
||||
let token;
|
||||
if (ssoToken) {
|
||||
token = ssoToken;
|
||||
} else {
|
||||
token = await getAuthToken();
|
||||
}
|
||||
if (!token) {
|
||||
return { success: false, error: 'M-Project 인증 실패' };
|
||||
}
|
||||
@@ -219,7 +226,7 @@ async function sendToMProject(issueData) {
|
||||
const requestBody = {
|
||||
category: mProjectCategory,
|
||||
description: enhancedDescription,
|
||||
project_id: M_PROJECT_CONFIG.defaultProjectId,
|
||||
project_id: project_id || M_PROJECT_CONFIG.defaultProjectId,
|
||||
};
|
||||
|
||||
// 사진 추가
|
||||
|
||||
Reference in New Issue
Block a user