diff --git a/api.hyungi.net/controllers/dailyIssueReportController.js b/api.hyungi.net/controllers/dailyIssueReportController.js index 281c834..cf0907e 100644 --- a/api.hyungi.net/controllers/dailyIssueReportController.js +++ b/api.hyungi.net/controllers/dailyIssueReportController.js @@ -1,110 +1,58 @@ -const dailyIssueReportModel = require('../models/dailyIssueReportModel'); +// /controllers/dailyIssueReportController.js +const dailyIssueReportService = require('../services/dailyIssueReportService'); -// 1. CREATE: 단일 또는 다중 등록 (worker_id 배열 지원) -exports.createDailyIssueReport = async (req, res) => { +/** + * 1. CREATE: 일일 이슈 보고서 생성 (Service Layer 사용) + */ +const createDailyIssueReport = async (req, res) => { try { - const body = req.body; - - // 기본 필드 - const base = { - date: body.date, - project_id: body.project_id, - start_time: body.start_time, - end_time: body.end_time, - issue_type_id: body.issue_type_id - }; - - if (!base.date || !base.project_id || !base.start_time || !base.end_time || !base.issue_type_id || !body.worker_id) { - return res.status(400).json({ error: '필수 필드 누락' }); - } - - // worker_id 배열화 - const workers = Array.isArray(body.worker_id) ? body.worker_id : [body.worker_id]; - const insertedIds = []; - - for (const wid of workers) { - const payload = { ...base, worker_id: wid }; - - const insertId = await new Promise((resolve, reject) => { - dailyIssueReportModel.create(payload, (err, id) => { - if (err) reject(err); - else resolve(id); - }); - }); - - insertedIds.push(insertId); - } - - res.json({ success: true, issue_report_ids: insertedIds }); + // 프론트엔드에서 worker_ids로 보내주기로 약속함 + const issueData = { ...req.body, worker_ids: req.body.worker_ids || req.body.worker_id }; + + const result = await dailyIssueReportService.createDailyIssueReportService(issueData); + + res.status(201).json({ success: true, ...result }); } catch (err) { - console.error('🔥 createDailyIssueReport error:', err); - res.status(500).json({ error: err.message || String(err) }); + console.error('💥 이슈 보고서 생성 컨트롤러 오류:', err); + res.status(400).json({ success: false, error: err.message }); } }; -// 2. READ BY DATE -exports.getDailyIssuesByDate = async (req, res) => { +/** + * 2. READ BY DATE: 날짜별 이슈 조회 (Service Layer 사용) + */ +const getDailyIssuesByDate = async (req, res) => { try { const { date } = req.query; - const rows = await new Promise((resolve, reject) => { - dailyIssueReportModel.getAllByDate(date, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - res.json(rows); + const issues = await dailyIssueReportService.getDailyIssuesByDateService(date); + res.json(issues); } catch (err) { - res.status(500).json({ error: err.message || String(err) }); + console.error('💥 이슈 보고서 조회 컨트롤러 오류:', err); + res.status(500).json({ success: false, error: err.message }); } }; -// 3. READ ONE -exports.getDailyIssueById = async (req, res) => { +/** + * 3. DELETE: 이슈 보고서 삭제 (Service Layer 사용) + */ +const removeDailyIssue = async (req, res) => { try { const { id } = req.params; - const row = await new Promise((resolve, reject) => { - dailyIssueReportModel.getById(id, (err, data) => { - if (err) reject(err); - else resolve(data); - }); - }); - if (!row) return res.status(404).json({ error: 'DailyIssueReport not found' }); - res.json(row); + const result = await dailyIssueReportService.removeDailyIssueService(id); + res.json({ success: true, ...result }); } catch (err) { - res.status(500).json({ error: err.message || String(err) }); + console.error('💥 이슈 보고서 삭제 컨트롤러 오류:', err); + const statusCode = err.statusCode || 500; + res.status(statusCode).json({ success: false, error: err.message }); } }; -// 4. UPDATE -exports.updateDailyIssue = async (req, res) => { - try { - const { id } = req.params; - const changes = await new Promise((resolve, reject) => { - dailyIssueReportModel.update(id, req.body, (err, affectedRows) => { - if (err) reject(err); - else resolve(affectedRows); - }); - }); - if (changes === 0) return res.status(404).json({ error: 'No changes or not found' }); - res.json({ success: true, changes }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +// 레거시 함수들은 더 이상 라우팅되지 않으므로 제거하거나 주석 처리 가능 +// exports.getDailyIssueById = ... +// exports.updateDailyIssue = ... -// 5. DELETE -exports.removeDailyIssue = async (req, res) => { - try { - const { id } = req.params; - const changes = await new Promise((resolve, reject) => { - dailyIssueReportModel.remove(id, (err, affectedRows) => { - if (err) reject(err); - else resolve(affectedRows); - }); - }); - if (changes === 0) return res.status(404).json({ error: 'DailyIssueReport not found' }); - res.json({ success: true, changes }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } +module.exports = { + createDailyIssueReport, + getDailyIssuesByDate, + removeDailyIssue, }; \ No newline at end of file diff --git a/api.hyungi.net/models/dailyIssueReportModel.js b/api.hyungi.net/models/dailyIssueReportModel.js index bf6c1ca..80683d1 100644 --- a/api.hyungi.net/models/dailyIssueReportModel.js +++ b/api.hyungi.net/models/dailyIssueReportModel.js @@ -1,51 +1,50 @@ const { getDb } = require('../dbPool'); /** - * 1. 등록 (단일 레코드) + * 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다. + * @param {Array} reports - 생성할 보고서 데이터 배열 + * @returns {Promise>} - 삽입된 ID 배열 */ -const create = async (report, callback) => { +const createMany = async (reports) => { + const db = await getDb(); + const conn = await db.getConnection(); try { - const db = await getDb(); - const { - date, - worker_id, - project_id, - start_time, - end_time, - issue_type_id, - description = null // 선택값 처리 - } = report; + await conn.beginTransaction(); - const [result] = await db.query( - `INSERT INTO DailyIssueReports - (date, worker_id, project_id, start_time, end_time, issue_type_id, description) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - [date, worker_id, project_id, start_time, end_time, issue_type_id, description] - ); + const insertedIds = []; + const sql = ` + INSERT INTO DailyIssueReports + (date, worker_id, project_id, start_time, end_time, issue_type_id) + VALUES (?, ?, ?, ?, ?, ?) + `; - callback(null, result.insertId); + for (const report of reports) { + const { date, worker_id, project_id, start_time, end_time, issue_type_id } = report; + const [result] = await conn.query(sql, [date, worker_id, project_id, start_time, end_time, issue_type_id]); + insertedIds.push(result.insertId); + } + + await conn.commit(); + return insertedIds; } catch (err) { - callback(err); + await conn.rollback(); + console.error('[Model] 여러 이슈 보고서 생성 중 오류:', err); + throw new Error('데이터베이스에 이슈 보고서를 생성하는 중 오류가 발생했습니다.'); + } finally { + conn.release(); } }; /** - * 2. 특정 날짜의 전체 이슈 목록 조회 + * 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반) */ -const getAllByDate = async (date, callback) => { +const getAllByDate = async (date) => { try { const db = await getDb(); const [rows] = await db.query( `SELECT - d.id, - d.date, - w.worker_name, - p.project_name, - d.start_time, - d.end_time, - t.category, - t.subcategory, - d.description + d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time, + t.category, t.subcategory, d.description FROM DailyIssueReports d LEFT JOIN Workers w ON d.worker_id = w.worker_id LEFT JOIN Projects p ON d.project_id = p.project_id @@ -54,9 +53,10 @@ const getAllByDate = async (date, callback) => { ORDER BY d.start_time ASC`, [date] ); - callback(null, rows); + return rows; } catch (err) { - callback(err); + console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err); + throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.'); } }; @@ -102,22 +102,53 @@ const update = async (id, data, callback) => { }; /** - * 5. 삭제 + * 5. 삭제 (Promise 기반) */ -const remove = async (id, callback) => { +const remove = async (id) => { try { const db = await getDb(); const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]); - callback(null, result.affectedRows); + return result.affectedRows; + } catch (err) { + console.error(`[Model] 이슈(id: ${id}) 삭제 오류:`, err); + throw new Error('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.'); + } +}; + +// V1 함수들은 점진적으로 제거 예정 +const create = async (report, callback) => { + try { + const db = await getDb(); + const { + date, + worker_id, + project_id, + start_time, + end_time, + issue_type_id, + description = null // 선택값 처리 + } = report; + + const [result] = await db.query( + `INSERT INTO DailyIssueReports + (date, worker_id, project_id, start_time, end_time, issue_type_id, description) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [date, worker_id, project_id, start_time, end_time, issue_type_id, description] + ); + + callback(null, result.insertId); } catch (err) { callback(err); } }; + module.exports = { - create, + createMany, // 신규 getAllByDate, + remove, + // 레거시 호환성을 위해 V1 함수들 임시 유지 + create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)), getById, update, - remove }; \ No newline at end of file diff --git a/api.hyungi.net/services/dailyIssueReportService.js b/api.hyungi.net/services/dailyIssueReportService.js new file mode 100644 index 0000000..abeb7d8 --- /dev/null +++ b/api.hyungi.net/services/dailyIssueReportService.js @@ -0,0 +1,93 @@ +// /services/dailyIssueReportService.js +const dailyIssueReportModel = require('../models/dailyIssueReportModel'); + +/** + * 일일 이슈 보고서를 생성하는 비즈니스 로직을 처리합니다. + * 한 번에 여러 작업자에 대해 동일한 이슈를 등록할 수 있습니다. + * @param {object} issueData - 컨트롤러에서 전달된 이슈 데이터 + * @returns {Promise} 생성 결과 + */ +const createDailyIssueReportService = async (issueData) => { + const { date, project_id, start_time, end_time, issue_type_id, worker_ids } = issueData; + + // 1. 유효성 검사 + if (!date || !project_id || !start_time || !end_time || !issue_type_id || !worker_ids) { + throw new Error('필수 필드가 누락되었습니다.'); + } + if (!Array.isArray(worker_ids) || worker_ids.length === 0) { + throw new Error('worker_ids는 최소 한 명 이상의 작업자를 포함하는 배열이어야 합니다.'); + } + + // 2. 모델에 전달할 데이터 준비 + const reportsToCreate = worker_ids.map(worker_id => ({ + date, + project_id, + start_time, + end_time, + issue_type_id, + worker_id + })); + + // 3. 모델 함수 호출 (모델에 createMany와 같은 함수가 필요) + try { + const insertedIds = await dailyIssueReportModel.createMany(reportsToCreate); + return { + message: `${insertedIds.length}개의 이슈 보고서가 성공적으로 생성되었습니다.`, + issue_report_ids: insertedIds + }; + } catch (error) { + console.error('[Service] 이슈 보고서 생성 중 오류 발생:', error); + throw error; + } +}; + +/** + * 특정 날짜의 모든 이슈 보고서를 조회합니다. + * @param {string} date - 조회할 날짜 (YYYY-MM-DD) + * @returns {Promise} 조회된 이슈 보고서 배열 + */ +const getDailyIssuesByDateService = async (date) => { + if (!date) { + throw new Error('조회를 위해 날짜(date)는 필수입니다.'); + } + try { + const issues = await dailyIssueReportModel.getAllByDate(date); + return issues; + } catch (error) { + console.error(`[Service] ${date}의 이슈 보고서 조회 중 오류 발생:`, error); + throw error; + } +}; + +/** + * 특정 ID의 이슈 보고서를 삭제합니다. + * @param {string} issueId - 삭제할 이슈 보고서의 ID + * @returns {Promise} 삭제 결과 + */ +const removeDailyIssueService = async (issueId) => { + if (!issueId) { + throw new Error('삭제를 위해 이슈 보고서 ID가 필요합니다.'); + } + try { + const affectedRows = await dailyIssueReportModel.remove(issueId); + if (affectedRows === 0) { + const notFoundError = new Error('삭제할 이슈 보고서를 찾을 수 없습니다.'); + notFoundError.statusCode = 404; + throw notFoundError; + } + return { + message: '이슈 보고서가 성공적으로 삭제되었습니다.', + deleted_id: issueId, + affected_rows: affectedRows + }; + } catch (error) { + console.error(`[Service] 이슈 보고서(id: ${issueId}) 삭제 중 오류 발생:`, error); + throw error; + } +}; + +module.exports = { + createDailyIssueReportService, + getDailyIssuesByDateService, + removeDailyIssueService, +}; \ No newline at end of file