refactor: 일일 작업 보고서 CRUD API 구조 개선

- dailyWorkReportController의 조회, 수정, 삭제(CRUD) 로직을 Service와 Model로 분리
 - Promise 기반의 async/await 구조로 비동기 코드 개선
 - 새로운 DB 스키마 v2의 명명 규칙 적용
This commit is contained in:
2025-07-28 11:19:28 +09:00
parent 97e32a7057
commit d154514fa0
3 changed files with 363 additions and 174 deletions

View File

@@ -845,6 +845,163 @@ const createReportEntries = async ({ report_date, worker_id, entries }) => {
}
};
/**
* [V2] 공통 SELECT 쿼리 (새로운 스키마 기준)
*/
const getSelectQueryV2 = () => `
SELECT
dwr.report_id,
dwr.report_date,
dwr.worker_id,
dwr.project_id,
dwr.task_id,
dwr.work_hours,
dwr.is_error,
dwr.error_type_code_id,
dwr.created_by_user_id,
w.worker_name,
p.project_name,
t.task_name,
c.code_name as error_type_name,
u.name as created_by_name,
dwr.created_at
FROM daily_work_reports dwr
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
LEFT JOIN projects p ON dwr.project_id = p.project_id
LEFT JOIN tasks t ON dwr.task_id = t.task_id
LEFT JOIN users u ON dwr.created_by_user_id = u.user_id
LEFT JOIN codes c ON dwr.error_type_code_id = c.code_id AND c.code_type_id = 'ERROR_TYPE'
`;
/**
* [V2] 옵션 기반으로 작업 보고서를 조회합니다. (Promise 기반)
* @param {object} options - 조회 조건 (date, worker_id, created_by_user_id 등)
* @returns {Promise<Array>} 조회된 작업 보고서 배열
*/
const getReportsWithOptions = async (options) => {
const db = await getDb();
let whereConditions = [];
let queryParams = [];
if (options.date) {
whereConditions.push('dwr.report_date = ?');
queryParams.push(options.date);
}
if (options.worker_id) {
whereConditions.push('dwr.worker_id = ?');
queryParams.push(options.worker_id);
}
if (options.created_by_user_id) {
whereConditions.push('dwr.created_by_user_id = ?');
queryParams.push(options.created_by_user_id);
}
// 필요에 따라 다른 조건 추가 가능 (project_id 등)
if (whereConditions.length === 0) {
throw new Error('조회 조건이 하나 이상 필요합니다.');
}
const whereClause = whereConditions.join(' AND ');
const sql = `${getSelectQueryV2()} WHERE ${whereClause} ORDER BY w.worker_name ASC, p.project_name ASC`;
try {
const [rows] = await db.query(sql, queryParams);
return rows;
} catch (err) {
console.error('[Model] 작업 보고서 조회 오류:', err);
throw new Error('데이터베이스에서 작업 보고서를 조회하는 중 오류가 발생했습니다.');
}
};
/**
* [V2] ID를 기준으로 특정 작업 보고서 항목을 수정합니다. (Promise 기반)
* @param {string} reportId - 수정할 보고서의 ID
* @param {object} updateData - 수정할 필드와 값
* @returns {Promise<number>} 영향을 받은 행의 수
*/
const updateReportById = async (reportId, updateData) => {
const db = await getDb();
// 허용된 필드 목록 (보안 및 안정성)
const allowedFields = ['project_id', 'task_id', 'work_hours', 'is_error', 'error_type_code_id'];
const setClauses = [];
const queryParams = [];
for (const field of allowedFields) {
if (updateData[field] !== undefined) {
setClauses.push(`${field} = ?`);
queryParams.push(updateData[field]);
}
}
// updated_by_user_id는 항상 업데이트
if (updateData.updated_by_user_id) {
setClauses.push('updated_by_user_id = ?');
queryParams.push(updateData.updated_by_user_id);
}
if (setClauses.length === 0) {
throw new Error('수정할 데이터가 없습니다.');
}
queryParams.push(reportId);
const sql = `UPDATE daily_work_reports SET ${setClauses.join(', ')} WHERE report_id = ?`;
try {
const [result] = await db.query(sql, queryParams);
return result.affectedRows;
} catch (err) {
console.error(`[Model] 작업 보고서 수정 오류 (id: ${reportId}):`, err);
throw new Error('데이터베이스에서 작업 보고서를 수정하는 중 오류가 발생했습니다.');
}
};
/**
* [V2] ID를 기준으로 특정 작업 보고서를 삭제합니다. (Promise 기반)
* @param {string} reportId - 삭제할 보고서 ID
* @param {number} deletedByUserId - 삭제를 수행하는 사용자 ID
* @returns {Promise<number>} 영향을 받은 행의 수
*/
const removeReportById = async (reportId, deletedByUserId) => {
const db = await getDb();
const conn = await db.getConnection();
try {
await conn.beginTransaction();
// 감사 로그를 위해 삭제 전 정보 조회
const [reportInfo] = await conn.query('SELECT * FROM daily_work_reports WHERE report_id = ?', [reportId]);
// 실제 삭제 작업
const [result] = await conn.query('DELETE FROM daily_work_reports WHERE report_id = ?', [reportId]);
// 감사 로그 추가 (삭제된 항목이 있고, 삭제자가 명시된 경우)
if (reportInfo.length > 0 && deletedByUserId) {
try {
await conn.query(
`INSERT INTO work_report_audit_log (action, report_id, old_values, changed_by, change_reason) VALUES (?, ?, ?, ?, ?)`,
['DELETE', reportId, JSON.stringify(reportInfo[0]), deletedByUserId, 'Manual deletion by user']
);
} catch (auditErr) {
console.warn('감사 로그 추가 실패:', auditErr.message);
// 감사 로그 실패가 전체 트랜잭션을 롤백시키지는 않음
}
}
await conn.commit();
return result.affectedRows;
} catch (err) {
await conn.rollback();
console.error(`[Model] 작업 보고서 삭제 오류 (id: ${reportId}):`, err);
throw new Error('데이터베이스에서 작업 보고서를 삭제하는 중 오류가 발생했습니다.');
} finally {
conn.release();
}
};
// 모든 함수 내보내기 (기존 기능 + 누적 기능)
module.exports = {
// 📋 마스터 데이터
@@ -880,5 +1037,8 @@ module.exports = {
getStatistics,
// 새로 추가된 V2 함수
createReportEntries
createReportEntries,
getReportsWithOptions,
updateReportById,
removeReportById
};