- 00~11: WTM 시수관리 시스템 설계 문서 (아키텍처, DB스키마, API스펙 등) - 12: BE 멀티프로젝트 플랫폼 구성 계획 (wbx-spring-core 라이브러리 전환) - 13: FE Vue3+PrimeVue4 모듈 기반 구조 계획 - 14: 레이아웃 표준 및 디자인 시스템 (반응형, 하드코딩 제거) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
186 줄
5.4 KiB
Markdown
186 줄
5.4 KiB
Markdown
# 06. 리포트 모듈
|
|
|
|
## PH1-1차 리포트 (2종)
|
|
|
|
### 1. 프로젝트별 시수 분석 (No.82)
|
|
|
|
```
|
|
GET /api/reports/project-hours
|
|
?projectId=1
|
|
&from=2025-04-01
|
|
&to=2025-05-31
|
|
&groupBy=month // month, week, discipline
|
|
&format=json // json, excel
|
|
```
|
|
|
|
```java
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
@Transactional(readOnly = true)
|
|
public class ReportService {
|
|
|
|
private final TimesheetEntryRepository entryRepository;
|
|
|
|
public ProjectHoursReport getProjectHoursReport(ProjectHoursFilter filter) {
|
|
// QueryDSL 동적 쿼리
|
|
QTimesheetEntry e = QTimesheetEntry.timesheetEntry;
|
|
QTimesheet ts = QTimesheet.timesheet;
|
|
QUser u = QUser.user;
|
|
|
|
var query = queryFactory
|
|
.select(Projections.constructor(ProjectHoursRow.class,
|
|
e.epcProject.projectCode,
|
|
e.epcProject.name,
|
|
u.discipline,
|
|
e.entryDate,
|
|
e.hours.sum()
|
|
))
|
|
.from(e)
|
|
.join(e.timesheet, ts)
|
|
.join(ts.user, u)
|
|
.where(
|
|
ts.status.eq(TimesheetStatus.APPROVED),
|
|
epcProjectIdEq(filter.getProjectId()),
|
|
entryDateBetween(filter.getFrom(), filter.getTo())
|
|
)
|
|
.groupBy(e.epcProject.projectCode, e.epcProject.name,
|
|
u.discipline, e.entryDate);
|
|
|
|
List<ProjectHoursRow> rows = query.fetch();
|
|
|
|
return ProjectHoursReport.builder()
|
|
.filter(filter)
|
|
.rows(rows)
|
|
.totalHours(rows.stream()
|
|
.map(ProjectHoursRow::getHours)
|
|
.reduce(BigDecimal.ZERO, BigDecimal::add))
|
|
.generatedAt(LocalDateTime.now())
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. WBS Level별 시수 분석 (No.83)
|
|
|
|
```
|
|
GET /api/reports/wbs-hours
|
|
?projectId=1
|
|
&wbsLevel=3 // 1~5
|
|
&from=2025-04-01
|
|
&to=2025-05-31
|
|
&format=json
|
|
```
|
|
|
|
```java
|
|
public WbsHoursReport getWbsHoursReport(WbsHoursFilter filter) {
|
|
var rows = queryFactory
|
|
.select(Projections.constructor(WbsHoursRow.class,
|
|
cw.wbsCode,
|
|
cw.name,
|
|
cw.level,
|
|
cw.discipline,
|
|
e.hours.sum(),
|
|
ts.user.countDistinct()
|
|
))
|
|
.from(e)
|
|
.join(e.timesheet, ts)
|
|
.join(e.canonicalWbs, cw)
|
|
.where(
|
|
ts.status.eq(TimesheetStatus.APPROVED),
|
|
cw.projectId.eq(filter.getProjectId()),
|
|
cw.level.eq(filter.getWbsLevel()),
|
|
entryDateBetween(filter.getFrom(), filter.getTo())
|
|
)
|
|
.groupBy(cw.wbsCode, cw.name, cw.level, cw.discipline)
|
|
.orderBy(cw.wbsCode.asc())
|
|
.fetch();
|
|
|
|
return WbsHoursReport.builder()
|
|
.filter(filter)
|
|
.rows(rows)
|
|
.build();
|
|
}
|
|
```
|
|
|
|
## PH1-2차 리포트 (2종)
|
|
|
|
### 3. Phase별 시수 비율 (No.85)
|
|
|
|
```
|
|
GET /api/reports/phase-ratio?projectId=1&from=...&to=...
|
|
```
|
|
|
|
### 4. Non-Project 시수 비율 (No.86)
|
|
|
|
```
|
|
GET /api/reports/np-ratio?department=Engineering&from=...&to=...
|
|
```
|
|
|
|
## Excel Export
|
|
|
|
```java
|
|
@Service
|
|
public class ReportExcelExporter {
|
|
|
|
public byte[] exportProjectHours(ProjectHoursReport report) {
|
|
try (Workbook wb = new XSSFWorkbook()) {
|
|
Sheet sheet = wb.createSheet("프로젝트별 시수");
|
|
|
|
// 헤더
|
|
CellStyle headerStyle = createHeaderStyle(wb);
|
|
Row header = sheet.createRow(0);
|
|
String[] headers = {"프로젝트코드", "프로젝트명", "Discipline", "날짜", "시수(h)"};
|
|
for (int i = 0; i < headers.length; i++) {
|
|
Cell cell = header.createCell(i);
|
|
cell.setCellValue(headers[i]);
|
|
cell.setCellStyle(headerStyle);
|
|
}
|
|
|
|
// 데이터
|
|
int rowIdx = 1;
|
|
for (ProjectHoursRow row : report.getRows()) {
|
|
Row r = sheet.createRow(rowIdx++);
|
|
r.createCell(0).setCellValue(row.getProjectCode());
|
|
r.createCell(1).setCellValue(row.getProjectName());
|
|
r.createCell(2).setCellValue(row.getDiscipline());
|
|
r.createCell(3).setCellValue(row.getDate().toString());
|
|
r.createCell(4).setCellValue(row.getHours().doubleValue());
|
|
}
|
|
|
|
// 합계 행
|
|
Row totalRow = sheet.createRow(rowIdx);
|
|
totalRow.createCell(3).setCellValue("합계");
|
|
totalRow.createCell(4).setCellValue(report.getTotalHours().doubleValue());
|
|
|
|
// Auto-size
|
|
for (int i = 0; i < headers.length; i++) sheet.autoSizeColumn(i);
|
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
wb.write(out);
|
|
return out.toByteArray();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## REST API (리포트)
|
|
|
|
```
|
|
# PH1-1차
|
|
GET /api/reports/project-hours 프로젝트별 시수 분석
|
|
GET /api/reports/project-hours/export Excel 다운로드
|
|
GET /api/reports/wbs-hours WBS Level별 시수 분석
|
|
GET /api/reports/wbs-hours/export Excel 다운로드
|
|
|
|
# PH1-2차
|
|
GET /api/reports/phase-ratio Phase별 시수 비율
|
|
GET /api/reports/np-ratio Non-Project 시수 비율
|
|
GET /api/reports/wbs-version-history WBS 버전 이력 조회
|
|
|
|
# 공통 파라미터
|
|
# projectId - 프로젝트 ID (필수/선택)
|
|
# from, to - 기간 (ISO 8601)
|
|
# groupBy - 집계 기준 (month, week, discipline)
|
|
# format - 응답 형식 (json, excel)
|
|
```
|