Files
tk-factory-services/system1-factory/api/services/workAnalysisService.js
Hyungi Ahn 550633b89d feat: 3-System 분리 프로젝트 초기 코드 작성
TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로
분리하기 위한 전체 코드 구조 작성.
- SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원)
- System 1: 공장관리 (TK-FB 기반, 신고 코드 제거)
- System 2: 신고 (TK-FB에서 workIssue 코드 추출)
- System 3: 부적합관리 (M-Project 기반)
- Gateway 포털 (path-based 라우팅)
- 통합 docker-compose.yml 및 배포 스크립트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:40:11 +09:00

110 lines
4.3 KiB
JavaScript

/**
* Work Analysis Service
*
* Handles complex business logic and data aggregation for detailed work analysis reports
*
* @author TK-FB-Project
* @since 2025-12-19
*/
const { getDb } = require('../dbPool');
const WorkAnalysis = require('../models/WorkAnalysis');
const logger = require('../utils/logger');
const { DatabaseError } = require('../utils/errors');
class WorkAnalysisService {
constructor() {
this.db = null;
this.model = null;
}
async init() {
if (!this.db) {
this.db = await getDb();
this.model = new WorkAnalysis(this.db);
}
}
/**
* 프로젝트별-작업별 시간 분석 및 집계 (Service Layer logic)
*/
async getProjectWorkTypeAnalysis(startDate, endDate) {
await this.init();
try {
// 1. Get Raw Data from Model
const rawData = await this.model.getProjectWorkTypeRawData(startDate, endDate);
// 2. Process and Group Data
const groupedData = {};
rawData.forEach(row => {
const projectKey = `${row.project_id}_${row.project_name}`;
if (!groupedData[projectKey]) {
groupedData[projectKey] = {
project_id: row.project_id,
project_name: row.project_name,
job_no: row.job_no,
total_project_hours: 0,
total_regular_hours: 0,
total_error_hours: 0,
work_types: []
};
}
// Project Totals Accumulation
groupedData[projectKey].total_project_hours += parseFloat(row.total_hours);
groupedData[projectKey].total_regular_hours += parseFloat(row.regular_hours);
groupedData[projectKey].total_error_hours += parseFloat(row.error_hours);
// Add WorkType Entry
groupedData[projectKey].work_types.push({
work_type_id: row.work_type_id,
work_type_name: row.work_type_name,
total_hours: parseFloat(row.total_hours),
regular_hours: parseFloat(row.regular_hours),
error_hours: parseFloat(row.error_hours),
total_reports: row.total_reports,
regular_reports: row.regular_reports,
error_reports: row.error_reports,
error_rate_percent: parseFloat(row.error_rate_percent) || 0
});
});
// 3. Calculate Project Error Rates
Object.values(groupedData).forEach(project => {
project.project_error_rate = project.total_project_hours > 0
? Math.round((project.total_error_hours / project.total_project_hours) * 100 * 100) / 100
: 0;
});
// 4. Calculate Grand Totals
const totalStats = {
total_projects: Object.keys(groupedData).length,
total_work_types: new Set(rawData.map(r => r.work_type_id)).size,
grand_total_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_project_hours, 0),
grand_regular_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_regular_hours, 0),
grand_error_hours: Object.values(groupedData).reduce((sum, p) => sum + p.total_error_hours, 0)
};
totalStats.grand_error_rate = totalStats.grand_total_hours > 0
? Math.round((totalStats.grand_error_hours / totalStats.grand_total_hours) * 100 * 100) / 100
: 0;
return {
summary: totalStats,
projects: Object.values(groupedData),
period: { start: startDate, end: endDate }
};
} catch (error) {
logger.error('Service: 프로젝트별-작업별 시간 분석 처리 실패', { error: error.message });
// Re-throw as DatabaseError to maintain consistency with controller expectation, or custom ServiceError
throw new DatabaseError(`분석 데이터 처리 중 오류 발생: ${error.message}`);
}
}
}
module.exports = new WorkAnalysisService();