feat: 일일 작업 보고서 생성 API 구조 개선 (C-S-M 패턴 적용)
- dailyWorkReportController의 생성 로직을 Service와 Model로 분리 - Service: 데이터 유효성 검사 등 비즈니스 로직 담당 - Model: 트랜잭션을 사용한 DB 쿼리 담당 - Controller: HTTP 요청/응답 처리만 담당하도록 단순화
This commit is contained in:
@@ -1,122 +1,38 @@
|
||||
// controllers/dailyWorkReportController.js - 권한별 전체 조회 지원 버전
|
||||
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
|
||||
const dailyWorkReportService = require('../services/dailyWorkReportService');
|
||||
|
||||
/**
|
||||
* 📝 작업보고서 생성 (누적 방식 - 덮어쓰기 없음!)
|
||||
* 📝 작업보고서 생성 (V2 - Service Layer 사용)
|
||||
*/
|
||||
const createDailyWorkReport = (req, res) => {
|
||||
const { report_date, worker_id, work_entries } = req.body;
|
||||
const created_by = req.user?.user_id || req.user?.id;
|
||||
const created_by_name = req.user?.name || req.user?.username || '알 수 없는 사용자';
|
||||
const createDailyWorkReport = async (req, res) => {
|
||||
try {
|
||||
const reportData = {
|
||||
...req.body,
|
||||
created_by: req.user?.user_id || req.user?.id,
|
||||
created_by_name: req.user?.name || req.user?.username || '알 수 없는 사용자'
|
||||
};
|
||||
|
||||
// 1. 기본 유효성 검사
|
||||
if (!report_date || !worker_id || !work_entries) {
|
||||
return res.status(400).json({
|
||||
error: '필수 필드가 누락되었습니다.',
|
||||
required: ['report_date', 'worker_id', 'work_entries'],
|
||||
received: {
|
||||
report_date: !!report_date,
|
||||
worker_id: !!worker_id,
|
||||
work_entries: !!work_entries
|
||||
}
|
||||
});
|
||||
}
|
||||
const result = await dailyWorkReportService.createDailyWorkReportService(reportData);
|
||||
|
||||
if (!Array.isArray(work_entries) || work_entries.length === 0) {
|
||||
return res.status(400).json({
|
||||
error: '최소 하나의 작업 항목이 필요합니다.',
|
||||
received_entries: work_entries?.length || 0
|
||||
});
|
||||
}
|
||||
|
||||
if (!created_by) {
|
||||
return res.status(401).json({
|
||||
error: '사용자 인증 정보가 없습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 작업 항목 유효성 검사
|
||||
for (let i = 0; i < work_entries.length; i++) {
|
||||
const entry = work_entries[i];
|
||||
const requiredFields = ['project_id', 'work_type_id', 'work_status_id', 'work_hours'];
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (entry[field] === undefined || entry[field] === null || entry[field] === '') {
|
||||
return res.status(400).json({
|
||||
error: `작업 항목 ${i + 1}의 ${field}가 누락되었습니다.`,
|
||||
entry_index: i,
|
||||
missing_field: field
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 에러 상태인 경우 에러 타입 필수
|
||||
if (entry.work_status_id === 2 && (!entry.error_type_id)) {
|
||||
return res.status(400).json({
|
||||
error: `작업 항목 ${i + 1}이 에러 상태인 경우 error_type_id가 필요합니다.`,
|
||||
entry_index: i
|
||||
});
|
||||
}
|
||||
|
||||
// 시간 유효성 검사
|
||||
const hours = parseFloat(entry.work_hours);
|
||||
if (isNaN(hours) || hours < 0 || hours > 24) {
|
||||
return res.status(400).json({
|
||||
error: `작업 항목 ${i + 1}의 작업시간이 유효하지 않습니다. (0-24시간)`,
|
||||
entry_index: i,
|
||||
received_hours: entry.work_hours
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 총 시간 계산
|
||||
const total_hours = work_entries.reduce((sum, entry) => sum + (parseFloat(entry.work_hours) || 0), 0);
|
||||
|
||||
// 4. 요청 데이터 구성
|
||||
const reportData = {
|
||||
report_date,
|
||||
worker_id: parseInt(worker_id),
|
||||
work_entries,
|
||||
created_by,
|
||||
created_by_name,
|
||||
total_hours,
|
||||
is_update: false
|
||||
};
|
||||
|
||||
console.log('📝 작업보고서 누적 추가 요청:', {
|
||||
date: report_date,
|
||||
worker: worker_id,
|
||||
creator: created_by_name,
|
||||
creator_id: created_by,
|
||||
entries: work_entries.length,
|
||||
total_hours
|
||||
});
|
||||
|
||||
// 5. 누적 추가 실행 (덮어쓰기 없음!)
|
||||
dailyWorkReportModel.createDailyReport(reportData, (err, result) => {
|
||||
if (err) {
|
||||
console.error('작업보고서 생성 오류:', err);
|
||||
return res.status(500).json({
|
||||
error: '작업보고서 생성 중 오류가 발생했습니다.',
|
||||
details: err.message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ 작업보고서 누적 추가 성공:', result);
|
||||
res.status(201).json({
|
||||
message: '작업보고서가 성공적으로 누적 추가되었습니다.',
|
||||
report_date,
|
||||
worker_id,
|
||||
created_by: created_by_name,
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
...result
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 작업보고서 생성 컨트롤러 오류:', error.message);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: '작업보고서 생성에 실패했습니다.',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 📊 누적 현황 조회 (새로운 기능)
|
||||
* <EFBFBD><EFBFBD> 누적 현황 조회 (새로운 기능)
|
||||
*/
|
||||
const getAccumulatedReports = (req, res) => {
|
||||
const { date, worker_id } = req.query;
|
||||
|
||||
@@ -794,6 +794,57 @@ const getStatistics = async (start_date, end_date, callback) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [V2] 여러 작업 보고서 항목을 트랜잭션으로 생성합니다. (Promise 기반)
|
||||
* @param {object} modelData - 서비스 레이어에서 전달된 데이터
|
||||
* @returns {Promise<object>} 삽입된 항목의 ID 배열과 개수
|
||||
*/
|
||||
const createReportEntries = async ({ report_date, worker_id, entries }) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
const insertedIds = [];
|
||||
const sql = `
|
||||
INSERT INTO daily_work_reports
|
||||
(report_date, worker_id, project_id, task_id, work_hours, is_error, error_type_code_id, created_by_user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
for (const entry of entries) {
|
||||
const { project_id, task_id, work_hours, is_error, error_type_code_id, created_by_user_id } = entry;
|
||||
const [result] = await conn.query(sql, [
|
||||
report_date,
|
||||
worker_id,
|
||||
project_id,
|
||||
task_id,
|
||||
work_hours,
|
||||
is_error,
|
||||
error_type_code_id,
|
||||
created_by_user_id
|
||||
]);
|
||||
insertedIds.push(result.insertId);
|
||||
}
|
||||
|
||||
await conn.commit();
|
||||
|
||||
console.log(`[Model] ${insertedIds.length}개 작업 항목 생성 완료.`);
|
||||
return {
|
||||
inserted_ids: insertedIds,
|
||||
inserted_count: insertedIds.length
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error('[Model] 작업 보고서 생성 중 오류 발생:', err);
|
||||
// 여기서 발생한 에러는 서비스 레이어로 전파됩니다.
|
||||
throw new Error('데이터베이스에 작업 보고서를 생성하는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
// 모든 함수 내보내기 (기존 기능 + 누적 기능)
|
||||
module.exports = {
|
||||
// 📋 마스터 데이터
|
||||
@@ -826,5 +877,8 @@ module.exports = {
|
||||
updateById,
|
||||
removeById,
|
||||
removeByDateAndWorker,
|
||||
getStatistics
|
||||
getStatistics,
|
||||
|
||||
// 새로 추가된 V2 함수
|
||||
createReportEntries
|
||||
};
|
||||
84
api.hyungi.net/services/dailyWorkReportService.js
Normal file
84
api.hyungi.net/services/dailyWorkReportService.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const dailyWorkReportModel = require('../models/dailyWorkReportModel');
|
||||
|
||||
/**
|
||||
* 일일 작업 보고서를 생성하는 비즈니스 로직을 처리합니다.
|
||||
* @param {object} reportData - 컨트롤러에서 전달된 보고서 데이터
|
||||
* @returns {Promise<object>} 생성 결과 또는 에러
|
||||
*/
|
||||
const createDailyWorkReportService = async (reportData) => {
|
||||
const { report_date, worker_id, work_entries, created_by, created_by_name } = reportData;
|
||||
|
||||
// 1. 기본 유효성 검사
|
||||
if (!report_date || !worker_id || !work_entries) {
|
||||
throw new Error('필수 필드(report_date, worker_id, work_entries)가 누락되었습니다.');
|
||||
}
|
||||
|
||||
if (!Array.isArray(work_entries) || work_entries.length === 0) {
|
||||
throw new Error('최소 하나의 작업 항목(work_entries)이 필요합니다.');
|
||||
}
|
||||
|
||||
if (!created_by) {
|
||||
throw new Error('사용자 인증 정보(created_by)가 없습니다.');
|
||||
}
|
||||
|
||||
// 2. 작업 항목 유효성 검사
|
||||
for (let i = 0; i < work_entries.length; i++) {
|
||||
const entry = work_entries[i];
|
||||
const requiredFields = ['project_id', 'task_id', 'work_hours'];
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (entry[field] === undefined || entry[field] === null || entry[field] === '') {
|
||||
throw new Error(`작업 항목 ${i + 1}의 '${field}' 필드가 누락되었습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
// is_error가 true일 때 error_type_code_id 필수 검사
|
||||
if (entry.is_error === true && !entry.error_type_code_id) {
|
||||
throw new Error(`작업 항목 ${i + 1}이 에러 상태인 경우 'error_type_code_id'가 필요합니다.`);
|
||||
}
|
||||
|
||||
const hours = parseFloat(entry.work_hours);
|
||||
if (isNaN(hours) || hours <= 0 || hours > 24) {
|
||||
throw new Error(`작업 항목 ${i + 1}의 작업 시간이 유효하지 않습니다 (0 초과 24 이하).`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 모델에 전달할 데이터 준비
|
||||
const modelData = {
|
||||
report_date,
|
||||
worker_id: parseInt(worker_id),
|
||||
entries: work_entries.map(entry => ({
|
||||
project_id: entry.project_id,
|
||||
task_id: entry.task_id,
|
||||
work_hours: parseFloat(entry.work_hours),
|
||||
is_error: entry.is_error || false,
|
||||
error_type_code_id: entry.error_type_code_id || null,
|
||||
created_by_user_id: created_by
|
||||
}))
|
||||
};
|
||||
|
||||
console.log('📝 [Service] 작업보고서 생성 요청:', {
|
||||
date: report_date,
|
||||
worker: worker_id,
|
||||
creator: created_by_name,
|
||||
entries_count: modelData.entries.length
|
||||
});
|
||||
|
||||
// 4. 모델 함수 호출
|
||||
try {
|
||||
const result = await dailyWorkReportModel.createReportEntries(modelData);
|
||||
console.log('✅ [Service] 작업보고서 생성 성공:', result);
|
||||
return {
|
||||
message: '작업보고서가 성공적으로 생성되었습니다.',
|
||||
...result
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Service] 작업보고서 생성 중 오류 발생:', error);
|
||||
// 모델에서 발생한 오류를 그대로 상위로 전달
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createDailyWorkReportService,
|
||||
};
|
||||
Reference in New Issue
Block a user