Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDING-97] 동아리 지원 폼지 통계 전체조회 API 구현 #242

Merged
merged 18 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ddingdong.ddingdongBE.common.utils;

public class CalculationUtils {

public static int calculateRatio(int numerator, int denominator) {
if (denominator == 0) {
return 0;
}
return (int) ((double) numerator / denominator * 100);
}

public static int calculateDifference(int beforeCount, int count) {
return count - beforeCount;
}

public static int calculateDifferenceRatio(int beforeCount, int count) {
int difference = calculateDifference(beforeCount, count);
return calculateRatio(difference, beforeCount);
}
}
41 changes: 23 additions & 18 deletions src/main/java/ddingdong/ddingdongBE/common/utils/TimeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,36 @@

public class TimeUtils {

private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm";
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm";

public static LocalDateTime parseToLocalDateTime(String dateString) {
if (dateString == null || dateString.isBlank()) {
return null;
public static LocalDateTime parseToLocalDateTime(String dateString) {
if (dateString == null || dateString.isBlank()) {
return null;
}

return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(DATE_FORMAT));
}

return LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(DATE_FORMAT));
}
public static LocalDateTime processDate(String dateString, LocalDateTime currentDate) {
if (dateString == null) {
return null;
}

public static LocalDateTime processDate(String dateString, LocalDateTime currentDate) {
if (dateString == null) {
return null;
}
if (dateString.isBlank()) {
return null;
}

if (dateString.isBlank()) {
return null;
return parseToLocalDateTime(dateString);
}

return parseToLocalDateTime(dateString);
}
public static boolean isDateInRange(LocalDate nowDate, LocalDate startDate, LocalDate endDate) {
if (nowDate == null || startDate == null || endDate == null) {
return false;
}
return !nowDate.isBefore(startDate) && !nowDate.isAfter(endDate);
}

public static boolean isDateInRange(LocalDate nowDate, LocalDate startDate, LocalDate endDate) {
if (nowDate == null || startDate == null || endDate == null) {
return false;
public static String getYearAndMonth(LocalDate date) {
return date.getYear() + "-" + date.getMonthValue();
}
return !nowDate.isBefore(startDate) && !nowDate.isAfter(endDate); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ddingdong.ddingdongBE.domain.form.controller.dto.request.UpdateFormRequest;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormListResponse;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormResponse;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormStatisticsResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -68,17 +69,24 @@ void deleteForm(
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms")
List<FormListResponse> getAllMyForm(
@AuthenticationPrincipal PrincipalDetails principalDetails
);
List<FormListResponse> getAllMyForm(@AuthenticationPrincipal PrincipalDetails principalDetails);

@Operation(summary = "동아리 지원 폼지 상세조회 API")
@ApiResponse(responseCode = "200", description = "동아리 지원 폼지 상세조회 성공",
content = @Content(schema = @Schema(implementation = FormResponse.class)))
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms/{formId}")
FormResponse getForm(
@PathVariable("formId") Long formId
FormResponse getForm(@PathVariable("formId") Long formId);

@Operation(summary = "동아리 지원 폼지 통계 전체조회 API")
@ApiResponse(responseCode = "200", description = "동아리 지원 폼지 통계 전체조회 성공",
content = @Content(schema = @Schema(implementation = FormStatisticsResponse.class)))
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms/{formId}/statistics")
FormStatisticsResponse getFormStatistics(
@PathVariable("formId") Long formId,
@AuthenticationPrincipal PrincipalDetails principalDetails
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import ddingdong.ddingdongBE.domain.form.controller.dto.request.UpdateFormRequest;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormListResponse;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormResponse;
import ddingdong.ddingdongBE.domain.form.controller.dto.response.FormStatisticsResponse;
import ddingdong.ddingdongBE.domain.form.service.FacadeCentralFormService;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormListQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery;
import ddingdong.ddingdongBE.domain.user.entity.User;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -58,4 +60,14 @@ public FormResponse getForm(Long formId) {
FormQuery query = facadeCentralFormService.getForm(formId);
return FormResponse.from(query);
}

@Override
public FormStatisticsResponse getFormStatistics(
Long formId,
PrincipalDetails principalDetails
) {
User user = principalDetails.getUser();
FormStatisticsQuery query = facadeCentralFormService.getStatisticsByForm(user, formId);
return FormStatisticsResponse.from(query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package ddingdong.ddingdongBE.domain.form.controller.dto.response;

import ddingdong.ddingdongBE.domain.form.entity.FieldType;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.ApplicantStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.DepartmentStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.FieldStatisticsQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.FieldStatisticsQuery.FieldStatisticsListQuery;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Builder;

@Builder
public record FormStatisticsResponse(
@Schema(description = "총 지원자 수", example = "50")
int totalCount,
@ArraySchema(schema = @Schema(implementation = DepartmentStatisticResponse.class))
List<DepartmentStatisticResponse> departmentStatistics,
@ArraySchema(schema = @Schema(implementation = ApplicantStatisticResponse.class))
List<ApplicantStatisticResponse> applicantStatistics,
@Schema(description = "필드 통계 전체조회", implementation = FieldStatisticsResponse.class)
FieldStatisticsResponse fieldStatistics
) {

@Builder
record DepartmentStatisticResponse(
@Schema(description = "학과 내 경쟁 순위", example = "1")
int rank,
@Schema(description = "학과명", example = "융합소프트웨어학부")
String label,
@Schema(description = "해당 학과의 지원자 수", example = "50")
int count,
@Schema(description = "전체 지원자 수 대비 비율", example = "30")
int ratio
) {

public static DepartmentStatisticResponse from(DepartmentStatisticQuery query) {
return DepartmentStatisticResponse.builder()
.rank(query.rank())
.label(query.label())
.count(query.count())
.ratio(query.ratio())
.build();
}
}

@Builder
record ApplicantStatisticResponse(
@Schema(description = "비교 년도 및 월", example = "2025-1")
String label,
@Schema(description = "해당 년도 및 학기 총 지원자수", example = "40")
int count,
@Schema(description = "전 폼지 대비 증감 값", example = "150")
CompareToBefore comparedToBefore
) {
record CompareToBefore(
@Schema(description = "증감율 %", example = "50")
int ratio,
@Schema(description = "증가수치 및 감소수치", example = "15")
int value
) {

}
public static ApplicantStatisticResponse from(ApplicantStatisticQuery query) {
return ApplicantStatisticResponse.builder()
.label(query.label())
.count(query.count())
.comparedToBefore(new CompareToBefore(query.compareRatio(), query.compareValue()))
.build();
}
}

@Builder
record FieldStatisticsResponse(
@Schema(description = "섹션종류", example = "[\"공통\"]")
List<String> sections,
@ArraySchema(schema = @Schema(implementation = FieldStatisticsListResponse.class))
List<FieldStatisticsListResponse> fields
) {
record FieldStatisticsListResponse(
@Schema(description = "폼지 질문 id", example = "1")
Long id,
@Schema(description = "폼지 질문", example = "당신 이름은 무엇인가요?")
String question,
@Schema(description = "폼지 질문에 대해 총 작성 개수", example = "20")
int count,
@Schema(description = "폼지 질문 유형", example = "CHECK_BOX")
FieldType type,
@Schema(description = "섹션", example = "공통")
String section
) {
public static FieldStatisticsListResponse from(FieldStatisticsListQuery query) {
return new FieldStatisticsListResponse(query.id(), query.question(), query.count(), query.fieldType(),
query.section());
}
}

public static FieldStatisticsResponse from(FieldStatisticsQuery query) {
List<FieldStatisticsListResponse> fieldStatisticsListResponses = query.fieldStatisticsListQueries().stream()
.map(FieldStatisticsListResponse::from)
.toList();
return FieldStatisticsResponse.builder()
.sections(query.sections())
.fields(fieldStatisticsListResponses)
.build();
}
}

public static FormStatisticsResponse from(FormStatisticsQuery query) {
List<DepartmentStatisticResponse> departmentStatisticResponse = query.departmentStatisticQueries().stream()
.map(DepartmentStatisticResponse::from)
.toList();
List<ApplicantStatisticResponse> applicantStatisticResponse = query.applicantStatisticQueries().stream()
.map(ApplicantStatisticResponse::from)
.toList();
return FormStatisticsResponse.builder()
.totalCount(query.totalCount())
.departmentStatistics(departmentStatisticResponse)
.applicantStatistics(applicantStatisticResponse)
.fieldStatistics(FieldStatisticsResponse.from(query.fieldStatisticsQuery()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@

import ddingdong.ddingdongBE.domain.form.entity.Form;
import ddingdong.ddingdongBE.domain.form.entity.FormField;
import ddingdong.ddingdongBE.domain.form.repository.dto.FieldListInfo;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface FormFieldRepository extends JpaRepository<FormField, Long> {

List<FormField> findAllByForm(Form form);

List<FormField> findAllByFormAndSection(Form form, String section);
@Query(value = """
SELECT f.id AS id, f.question AS question, f.field_type AS type, f.section AS section, COUNT(fa.id) AS count
FROM (
SELECT *
FROM form_field field
WHERE field.form_id = :formId
) AS f
LEFT JOIN form_answer AS fa
ON fa.field_id = f.id
GROUP BY f.id
ORDER BY f.id
""", nativeQuery = true)
KoSeonJe marked this conversation as resolved.
Show resolved Hide resolved
List<FieldListInfo> findFieldWithAnswerCountByFormId(@Param("formId") Long formId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ddingdong.ddingdongBE.domain.form.repository.dto;

import ddingdong.ddingdongBE.domain.form.entity.FieldType;

public interface FieldListInfo {

Long getId();

String getQuestion();

Integer getCount();

FieldType getType();

String getSection();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ddingdong.ddingdongBE.domain.form.service.dto.command.UpdateFormCommand;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormListQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery;
import ddingdong.ddingdongBE.domain.user.entity.User;
import java.util.List;

Expand All @@ -18,4 +19,6 @@ public interface FacadeCentralFormService {
List<FormListQuery> getAllMyForm(User user);

FormQuery getForm(Long formId);

FormStatisticsQuery getStatisticsByForm(User user, Long formId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import ddingdong.ddingdongBE.domain.form.service.dto.command.UpdateFormCommand.UpdateFormFieldCommand;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormListQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.ApplicantStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.DepartmentStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.FieldStatisticsQuery;
import ddingdong.ddingdongBE.domain.user.entity.User;
import java.time.LocalDate;
import java.util.List;
Expand All @@ -28,6 +32,7 @@ public class FacadeCentralFormServiceImpl implements FacadeCentralFormService {
private final FormService formService;
private final FormFieldService formFieldService;
private final ClubService clubService;
private final FormStatisticService formStatisTicService;
KoSeonJe marked this conversation as resolved.
Show resolved Hide resolved

@Transactional
@Override
Expand Down Expand Up @@ -79,6 +84,18 @@ public FormQuery getForm(Long formId) {
return FormQuery.of(form, formFields);
}

@Override
public FormStatisticsQuery getStatisticsByForm(User user, Long formId) {
Club club = clubService.getByUserId(user.getId());
Form form = formService.getById(formId);
int totalCount = formStatisTicService.getTotalApplicationCountByForm(form);
List<DepartmentStatisticQuery> departmentStatisticQueries = formStatisTicService.createDepartmentStatistics(totalCount, form);
List<ApplicantStatisticQuery> applicantStatisticQueries = formStatisTicService.createApplicationStatistics(club, form);
FieldStatisticsQuery fieldStatisticsQuery = formStatisTicService.createFieldStatisticsByForm(form);

return new FormStatisticsQuery(totalCount, departmentStatisticQueries, applicantStatisticQueries, fieldStatisticsQuery);
}

private FormListQuery buildFormListQuery(Form form) {
boolean isActive = TimeUtils.isDateInRange(LocalDate.now(), form.getStartDate(), form.getEndDate());
return FormListQuery.from(form, isActive);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ddingdong.ddingdongBE.domain.form.service;

import ddingdong.ddingdongBE.domain.club.entity.Club;
import ddingdong.ddingdongBE.domain.form.entity.Form;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.ApplicantStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.DepartmentStatisticQuery;
import ddingdong.ddingdongBE.domain.form.service.dto.query.FormStatisticsQuery.FieldStatisticsQuery;
import java.util.List;

public interface FormStatisticService {

int getTotalApplicationCountByForm(Form form);

List<DepartmentStatisticQuery> createDepartmentStatistics(int totalCount, Form form);

List<ApplicantStatisticQuery> createApplicationStatistics(Club club, Form form);

FieldStatisticsQuery createFieldStatisticsByForm(Form form);

}
Loading