Skip to content

Commit 055a30e

Browse files
committed
fix(core-attendance): csv 다운로드 기능 개선
1 parent 8b5d955 commit 055a30e

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

src/main/java/inha/gdgoc/domain/core/attendance/controller/CoreAttendanceController.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,26 @@ public ResponseEntity<ApiResponse<DaySummaryResponse, Void>> summary(@Authentica
118118
@GetMapping(value = "/{date}/summary.csv", produces = "text/csv; charset=UTF-8")
119119
public ResponseEntity<String> summaryCsv(@AuthenticationPrincipal CustomUserDetails me, @PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, @RequestParam(required = false) TeamType team) {
120120
TeamType effective = (me.getRole() == UserRole.LEAD && me.getTeam() != TeamType.HR) ? requiredTeamFrom(me) : team;
121-
String csv = service.buildSummaryCsv(date.toString(), effective); // 서비스에 구현
121+
String csv = service.buildSummaryCsv(date.toString(), effective);
122122
return ResponseEntity.ok()
123123
.header("Content-Disposition", "attachment; filename=\"attendance-" + date + ".csv\"")
124124
.body(csv);
125125
}
126+
127+
@GetMapping(value = "/summary.csv", produces = "text/csv; charset=UTF-8")
128+
public ResponseEntity<String> summaryCsvAll(
129+
@AuthenticationPrincipal CustomUserDetails me,
130+
@RequestParam(required = false) TeamType team
131+
) {
132+
// LEAD & not HR → 자신의 팀만
133+
TeamType effective = (me.getRole() == UserRole.LEAD && me.getTeam() != TeamType.HR)
134+
? requiredTeamFrom(me)
135+
: team;
136+
137+
String csv = service.buildFullMatrixCsv(effective);
138+
return ResponseEntity.ok()
139+
.header("Content-Disposition", "attachment; filename=\"attendance-summary.csv\"")
140+
.body(csv);
141+
}
142+
126143
}

src/main/java/inha/gdgoc/domain/core/attendance/service/CoreAttendanceService.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,68 @@ public String buildSummaryCsv(String date, TeamType teamOrNull) {
215215
return new String(sb.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
216216
}
217217

218+
@Transactional(readOnly = true)
219+
public String buildFullMatrixCsv(TeamType teamOrNull) {
220+
// 1) 날짜 목록(오름차순)
221+
List<LocalDate> dates = meetingRepository
222+
.findAll(Sort.by(Sort.Direction.ASC, "meetingDate"))
223+
.stream()
224+
.map(Meeting::getMeetingDate)
225+
.toList();
226+
227+
// 날짜가 없으면 헤더만
228+
if (dates.isEmpty()) {
229+
return "이름\n";
230+
}
231+
232+
// 2) 대상 사용자: 기존 정책과 동일 (CORE/LEAD/ORGANIZER), 팀 필터 적용
233+
var roles = List.of(UserRole.CORE, UserRole.LEAD, UserRole.ORGANIZER);
234+
List<User> users = (teamOrNull == null)
235+
? userRepository.findByUserRoleIn(roles)
236+
: userRepository.findByTeamAndUserRoleIn(teamOrNull, roles);
237+
238+
// 팀 없는 사용자 제외 + 이름순 정렬
239+
users = users.stream()
240+
.filter(u -> u.getTeam() != null)
241+
.sorted(Comparator.comparing(User::getName))
242+
.toList();
243+
244+
// 사용자 없으면 헤더만
245+
if (users.isEmpty()) {
246+
StringBuilder onlyHeader = new StringBuilder("이름");
247+
for (LocalDate d : dates) onlyHeader.append(',').append(d);
248+
onlyHeader.append('\n');
249+
return new String(onlyHeader.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
250+
}
251+
252+
// 3) 날짜별 출석 맵을 한 번에 수집 (N×M 쿼리 방지 필요시 Repository 확장 고려)
253+
// 여기서는 기존 getPresenceMap(LocalDate)를 재사용해 날짜 단위로 가져옴
254+
List<Map<Long, Boolean>> presenceByDate = new ArrayList<>(dates.size());
255+
for (LocalDate d : dates) {
256+
presenceByDate.add(getPresenceMap(d)); // userId -> present
257+
}
258+
259+
// 4) CSV 빌드
260+
StringBuilder sb = new StringBuilder();
261+
// Header
262+
sb.append("이름");
263+
for (LocalDate d : dates) sb.append(',').append(d);
264+
sb.append('\n');
265+
266+
// Rows
267+
for (User u : users) {
268+
sb.append(escape(u.getName()));
269+
Long uid = u.getId();
270+
for (Map<Long, Boolean> day : presenceByDate) {
271+
boolean present = Boolean.TRUE.equals(day.getOrDefault(uid, false));
272+
sb.append(',').append(present ? 'O' : 'X');
273+
}
274+
sb.append('\n');
275+
}
276+
277+
return new String(sb.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
278+
}
279+
218280
/* ===================== helpers ===================== */
219281

220282
/** date로 meeting을 보장하고 meetingId 반환 */

0 commit comments

Comments
 (0)