Skip to content

Commit

Permalink
Merge branch 'develop' into feature/COT-165-attendance-record-order
Browse files Browse the repository at this point in the history
  • Loading branch information
Youthhing authored Jan 22, 2025
2 parents bd30378 + e117414 commit 7011b9c
Show file tree
Hide file tree
Showing 22 changed files with 438 additions and 384 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRecordRequest;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRequest;
import org.cotato.csquiz.common.role.RoleAuthority;
import org.cotato.csquiz.domain.attendance.service.AttendanceExcelService;
import org.cotato.csquiz.domain.attendance.service.AttendanceRecordService;
import org.cotato.csquiz.domain.attendance.service.AttendanceService;
import org.cotato.csquiz.domain.attendance.util.AttendanceExcelHeaderUtil;
import org.cotato.csquiz.domain.auth.enums.MemberRole;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
Expand All @@ -39,7 +36,6 @@
@RequestMapping("/v2/api/attendances")
public class AttendanceController {

private final AttendanceExcelService attendanceExcelService;
private final AttendanceRecordService attendanceRecordService;
private final AttendanceService attendanceService;

Expand Down Expand Up @@ -95,18 +91,4 @@ public ResponseEntity<AttendancesResponse> findAttendancesByGeneration(
@RequestParam("generationId") Long generationId) {
return ResponseEntity.ok().body(attendanceService.findAttendancesByGenerationId(generationId));
}


@Operation(summary = "세션별 출석 기록 엑셀 다운로드 API")
@GetMapping("/excel")
public ResponseEntity<byte[]> downloadAttendanceRecordsAsExcelBySessions(
@RequestParam(name = "attendanceIds") List<Long> attendanceIds) {

byte[] excelFile = attendanceExcelService.createExcelForSessionAttendance(attendanceIds);
String finalFileName = attendanceExcelService.getEncodedFileName(attendanceIds);

HttpHeaders headers = AttendanceExcelHeaderUtil.createExcelDownloadHeaders(finalFileName);

return ResponseEntity.ok().headers(headers).body(excelFile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.cotato.csquiz.api.attendance.controller;

import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.common.poi.ExcelView;
import org.cotato.csquiz.domain.attendance.service.AttendanceExcelService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
@RequestMapping("/v2/api/attendances/records/excel")
@RequiredArgsConstructor
public class AttendanceExcelController {

private final AttendanceExcelService attendanceExcelService;

@Operation(summary = "세션별 출석 기록 엑셀 다운로드 API")
@GetMapping(params = "generationId")
public ModelAndView downloadAttendanceRecordsAsExcelBySessions(@RequestParam(name = "generationId") Long generationId) {
return new ModelAndView(new ExcelView(), attendanceExcelService.getAttendanceRecordsExcelDataByGeneration(generationId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,9 @@ public class AttendanceRecordController {

@Operation(summary = "대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "409", description = "이미 출석을 완료함"),
@ApiResponse(responseCode = "400", description = "출석 시간이 아님")
}
)
@PostMapping(value = "/offline")
Expand All @@ -54,18 +45,9 @@ public ResponseEntity<AttendResponse> submitOfflineAttendanceRecord(

@Operation(summary = "비대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "409", description = "이미 출석을 완료함"),
@ApiResponse(responseCode = "400", description = "출석 시간이 아님")
})
@PostMapping(value = "/online")
public ResponseEntity<AttendResponse> submitOnlineAttendanceRecord(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.api.member.dto.CreateGenerationMemberRequest;
import org.cotato.csquiz.api.member.dto.DeleteGenerationMemberRequest;
import org.cotato.csquiz.api.member.dto.GenerationMemberInfoResponse;
import org.cotato.csquiz.api.member.dto.UpdateGenerationMemberRoleRequest;
import org.cotato.csquiz.common.role.RoleAuthority;
Expand Down Expand Up @@ -52,8 +51,8 @@ public ResponseEntity<Void> updateGenerationMemberRole(

@RoleAuthority(MemberRole.ADMIN)
@DeleteMapping
public ResponseEntity<Void> deleteGenerationMember(@RequestBody @Valid DeleteGenerationMemberRequest request) {
generationMemberService.deleteGenerationMembers(request.generationMemberIds());
public ResponseEntity<Void> deleteGenerationMember(@RequestParam(name = "generationMemberId") Long generationMemberId) {
generationMemberService.deleteGenerationMember(generationMemberId);
return ResponseEntity.noContent().build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public enum ErrorCode {
WEBP_CONVERT_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "S-009", "webp 변환에 실패했습니다"),
GUILD_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "S-010", "디스코드 서버를 찾지 못했습니다."),
CHANNEL_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "S-011", "디스코드 채널을 찾지 못했습니다."),
DISCORD_BUTTON_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S-012" , "디스코드 버튼 이벤트 ID를 찾지 못했습니다."),
DISCORD_BUTTON_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S-012", "디스코드 버튼 이벤트 ID를 찾지 못했습니다."),
EMAIL_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S-013", "이메일 전송에 실패했습니다."),
FILE_GENERATION_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "S-014", "엑셀 파일 생성에 실패했습니다."),
;
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/cotato/csquiz/common/poi/CellData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.cotato.csquiz.common.poi;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor
public class CellData {

private String value;
}
13 changes: 13 additions & 0 deletions src/main/java/org/cotato/csquiz/common/poi/ExcelColumnName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.cotato.csquiz.common.poi;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumnName {

String headerName() default "";
}
10 changes: 10 additions & 0 deletions src/main/java/org/cotato/csquiz/common/poi/ExcelData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cotato.csquiz.common.poi;

import java.util.List;

public interface ExcelData {

List<CellData> headers();

List<CellData> datas();
}
20 changes: 20 additions & 0 deletions src/main/java/org/cotato/csquiz/common/poi/ExcelView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.cotato.csquiz.common.poi;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractXlsxView;

@Component
@RequiredArgsConstructor
public class ExcelView extends AbstractXlsxView {

@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
HttpServletResponse response) {
new ExcelWriter(model, workbook, response).create();
}
}
78 changes: 78 additions & 0 deletions src/main/java/org/cotato/csquiz/common/poi/ExcelWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.cotato.csquiz.common.poi;

import jakarta.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.http.HttpHeaders;

@RequiredArgsConstructor
public class ExcelWriter {

public static final String FILE_NAME = "fileName";
public static final String SHEETS = "sheets";

private final Map<String, Object> data;
private final Workbook workbook;
private final HttpServletResponse response;

@SuppressWarnings("unchecked")
public void create() {
setFileName(response, (String) data.get(FILE_NAME));

Map<String, ?> dataBySheet = (Map<String, ?>) data.get(SHEETS);

for (Entry<String, ?> sheetAndDatas : dataBySheet.entrySet()) {
String sheetName = sheetAndDatas.getKey();
Sheet sheet = workbook.createSheet(sheetName);
createHeaderRow(sheet, sheetAndDatas.getValue());
createDataRow(sheet, sheetAndDatas.getValue());
}
}

@SuppressWarnings("unchecked")
private void createHeaderRow(Sheet sheet, Object datas) {
Row headerRow = sheet.createRow(0);
List<ExcelData> excelData = (List<ExcelData>) datas;
int cellNumber = 0;
ExcelData cellData = excelData.get(0);
List<CellData> headers = cellData.headers();

for (CellData header : headers) {
Cell cell = headerRow.createCell(cellNumber++);
cell.setCellValue(header.getValue());
}
}

@SuppressWarnings("unchecked")
private void createDataRow(Sheet sheet, Object datas) {
List<ExcelData> excelData = (List<ExcelData>) datas;

int rowNumber = 1;
for (ExcelData columnValue : excelData) {
Row row = sheet.createRow(rowNumber++);
createData(row, columnValue.datas());
}
}

private void createData(Row row, List<CellData> datas) {
int columnNumber = 0;
for (CellData cellData : datas) {
Cell cell = row.createCell(columnNumber++);
cell.setCellValue(cellData.getValue());
}
}

private void setFileName(HttpServletResponse response, String fileName) {
String format = "attachment; filename=\"%s\"";
String encodedFilename = String.format(format, URLEncoder.encode(fileName +".xlsx", StandardCharsets.UTF_8));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, encodedFilename);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.cotato.csquiz.domain.attendance.poi;

import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.cotato.csquiz.common.poi.CellData;
import org.cotato.csquiz.common.poi.ExcelColumnName;
import org.cotato.csquiz.common.poi.ExcelData;
import org.cotato.csquiz.domain.attendance.entity.AttendanceRecord;
import org.cotato.csquiz.domain.attendance.enums.AttendanceResult;
import org.cotato.csquiz.domain.attendance.vo.AttendRecordStatics;
import org.cotato.csquiz.domain.auth.entity.Member;
import org.cotato.csquiz.domain.generation.entity.Session;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttendanceRecordExcelData implements ExcelData {

private static final String NAME_VALUE = "이름";
private static final String SESSION_COUNTS = "세션 수";
private static final String OFFLINE = "대면";
private static final String ONLINE = "비대면";
private static final String LATE = "지각";
private static final String ABSENT = "결석";

@ExcelColumnName(headerName = "이름")
private String name;

private List<AttendRecord> records;

@ExcelColumnName(headerName = "세션 수")
private String attendanceCounts;

@ExcelColumnName(headerName = "대면")
private String offline;

@ExcelColumnName(headerName = "비대면")
private String online;

@ExcelColumnName(headerName = "지각")
private String late;

@ExcelColumnName(headerName = "결석")
private String absent;

public static AttendanceRecordExcelData of(Member member, int size, List<AttendRecord> records) {
return AttendanceRecordExcelData.builder()
.name(member.getName())
.records(records)
.attendanceCounts(String.valueOf(size))
.build();
}

@Override
public List<CellData> headers() {
List<CellData> headers = new ArrayList<>();
headers.add(CellData.builder().value(NAME_VALUE).build());

for (AttendRecord record : records) {
headers.add(CellData.builder().value(record.sessionName).build());
}
headers.add(CellData.builder().value(SESSION_COUNTS).build());
headers.add(CellData.builder().value(OFFLINE).build());
headers.add(CellData.builder().value(ONLINE).build());
headers.add(CellData.builder().value(LATE).build());
headers.add(CellData.builder().value(ABSENT).build());

return headers;
}

@Override
public List<CellData> datas() {
List<CellData> datas = new ArrayList<>();
datas.add(CellData.builder().value(name).build());

for (AttendRecord record : records) {
datas.add(CellData.builder().value(record.result().getDescription()).build());
}
datas.add(CellData.builder().value(attendanceCounts).build());

AttendRecordStatics recordStatics = AttendRecordStatics.from(records);

datas.add(CellData.builder().value(String.valueOf(recordStatics.offline())).build());
datas.add(CellData.builder().value(String.valueOf(recordStatics.online())).build());
datas.add(CellData.builder().value(String.valueOf(recordStatics.late())).build());
datas.add(CellData.builder().value(String.valueOf(recordStatics.absent())).build());

return datas;
}

public record AttendRecord(
String sessionName,
AttendanceResult result
) {
public static AttendRecord of(Session session, AttendanceRecord attendanceRecord) {
return new AttendRecord(
session.getTitle(),
attendanceRecord.getAttendanceResult()
);
}
}
}
Loading

0 comments on commit 7011b9c

Please sign in to comment.