Skip to content

Conversation

@rlajm1203
Copy link
Contributor

@rlajm1203 rlajm1203 commented Sep 23, 2025

📌 관련 이슈

#285

✒️ 작업 내용

  • 전체 회원의 출석 통계 API를 구현하였습니다.
    • 관리자만 조회 가능하도록, 시큐리티 설정 적용
  • 부가적으로 attend/persistence 패키지 내에 있던 클래스들을 같은 도메인 별로 패키징 했습니다.

스크린샷 🏞️ (선택)

💬 REVIEWER에게 요구사항 💬

Summary by CodeRabbit

  • 신기능
    • 출결 통계 API 추가: GET /api/attend/statistic. 페이징 및 재직상태·기간 필터 지원, 회원별 지각/결석/패널티 합계 제공.
  • 보안
    • 출결 통계 API 접근에 관리자 권한이 필요합니다. 기존 권한 정책은 유지됩니다.
  • 문서
    • API 문서에 출결 통계 엔드포인트 명세를 추가했습니다.
  • 리팩터
    • 내부 패키지 구조 및 의존 경로를 정리했습니다. 기능 동작에는 변화가 없습니다.

@rlajm1203 rlajm1203 self-assigned this Sep 23, 2025
@rlajm1203 rlajm1203 added the feature 기능 개발 label Sep 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

GET /api/attend/statistic 엔드포인트가 추가되고 ADMIN 권한으로 보호됩니다. 통계 응답 DTO가 신설되었고, Usecase/Service/Repository에 통계 조회 로직과 페이지네이션이 추가되었습니다. 여러 persistence 컴포넌트가 policy/presentation/rank/teambuilding 하위 패키지로 리팩터링되어 관련 서비스/테스트의 import가 정리되었습니다.

Changes

Cohort / File(s) Summary
보안 규칙 업데이트
eeos/src/main/java/.../config/security/SecurityFilterChainConfig.java
GET /api/attend/statistic 엔드포인트에 ADMIN 역할 요구 규칙 추가.
출결 통계 기능(컨트롤러/서비스/레포/DTO/문서)
eeos/src/main/java/.../target/presentation/controller/AttendController.java, .../target/application/usecase/GetAttendantInfoUsecase.java, .../target/application/service/AttendService.java, .../target/persistence/AttendRepository.java, .../target/presentation/docs/AttendApi.java, .../target/application/dto/AttendStatisticsResponse.java
새로운 출결 통계 엔드포인트, Usecase 메서드, Service 로직, Repository 통계 쿼리 2종, DTO 및 API 문서 추가. 페이지네이션/정렬 및 기간·상태 필터 지원.
패키지 구조 정리 — policy/rank/presentation 이동
.../target/persistence/policy/* (Entity/Repository/Impl), .../target/application/repository/AttendWeightPolicyRepository.java, .../target/application/service/AttendWeightPolicyService.java, .../target/application/service/AttendWeightCalculator.java, .../target/persistence/rank/*, .../target/persistence/presentation/*, .../target/persistence/converter/PresentationConverter.java, .../team/application/Service/TeamService.java
정책/랭크/발표 관련 엔티티·레포지토리를 하위 패키지로 이동하고, 이에 따른 서비스/컨버터/테스트 import 경로 수정. 기능 변화 없음.
패키지 구조 정리 — teambuilding 이동
.../target/persistence/teambuilding/* (InputDataEntity, InputStatus, TargetEntity, TargetRepository), .../target/application/model/TeamBuildingTargetModel.java, .../target/application/model/converter/TeamBuildingTargetEntityConverter.java, .../target/application/service/AttendTeamBuildingService.java, .../target/application/service/QueryTeamBuildingTargetService.java, .../teamBuilding/application/service/CommandTeamBuildingTargetMemberService.java, .../teamBuilding/application/event/DeletedTeamBuildingEventListener.java
팀빌딩 관련 타입을 teambuilding 하위 패키지로 이동하고 연관 클래스들의 import 경로를 수정. 기능 변화 없음.
테스트 업데이트
eeos/src/test/java/.../AttendWeightPolicyServiceTest.java, .../SelectTeamBuildingCommandTargetMemberServiceTest.java
패키지 이동에 따른 import 경로 수정.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant C as Client
    participant S as SecurityFilterChain
    participant AC as AttendController
    participant U as GetAttendantInfoUsecase
    participant AS as AttendService
    participant R as AttendRepository

    C->>S: GET /api/attend/statistic?page&size&activeStatus&startDate&endDate
    alt 권한 확인
        S-->>C: 403 (ADMIN 아님)
    else ADMIN
        S-->>AC: 요청 전달
        AC->>U: getStatistics(page,size,activeStatus,startDate,endDate)
        U->>AS: getStatistics(...)
        AS->>R: getStatistics(start,end,late,absent[,activeStatus], pageable)
        R-->>AS: Page<Object[]> (id,name,status,late,absent,penalty)
        AS-->>U: PageResponse<MemberStatistics>
        U-->>AC: PageResponse<MemberStatistics>
        AC-->>C: 200 ApiResponse<SuccessBody<PageResponse<MemberStatistics>>>
    end
    note over AS,R: 새 통계 쿼리와 DTO 매핑
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Daae-Kim
  • kssumin

Poem

작은 발로 깡총깡총, 로그 위를 뛰어넘어
ADMIN 문을 톡톡—통계의 길이 열렸네!
DTO 주머니엔 멤버별 수치 한 움큼,
레포의 길은 가지런히 패키지로 이어지고,
오늘도 나는 코드밭의 토끼—페이징 꿈을 씹어요. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 "[FEAT] 전체 회원의 출석 통계 API 구현"은 PR의 핵심 변경사항인 전체 회원 대상 출석 통계 API 추가와 관련 리팩토링(패키지 재구성 및 보안 설정 추가)을 명확하고 간결하게 요약하고 있어 적절합니다. 엔드포인트 추가와 ADMIN 권한 적용 등 주요 변경을 정확히 반영하여 한눈에 의도를 파악할 수 있습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BM/feat/#285/attend-statistics

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Sep 23, 2025

Test Results

101 tests  ±0   98 ✅ ±0   5s ⏱️ ±0s
 40 suites ±0    3 💤 ±0 
 40 files   ±0    0 ❌ ±0 

Results for commit 64a3207. ± Comparison against base commit 13c86d6.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai bot added the 🔧refactor 코드 수정 label Sep 23, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (19)
eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/event/DeletedTeamBuildingEventListener.java (1)

24-26: 로그 메시지 개선 제안: eventId 포함 및 의미 명확화

AFTER_COMMIT 리스너 시작 시점에 eventId와 txActive를 함께 남기면 트레이싱에 유용합니다.

-        log.info(
-                "팀빌딩 삭제 Transaction committed: {}",
-                TransactionSynchronizationManager.isActualTransactionActive());
+        log.info("팀빌딩 삭제 처리 시작 - eventId={}, txActive={}",
+                event.getTeamBuildingId(),
+                TransactionSynchronizationManager.isActualTransactionActive());
eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/service/CommandTeamBuildingTargetMemberService.java (1)

41-41: 대량 저장 시 saveAllInBatch 고려

대상 수가 큰 경우 saveAll 대신 saveAllInBatch로 변경하면 flush/dirty checking 오버헤드를 줄일 수 있습니다. (리포지토리가 JpaRepository 계층을 상속하는 경우)

-        targetRepository.saveAll(targetEntities);
+        targetRepository.saveAllInBatch(targetEntities);
eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/JpaWeightPolicyRepository.java (1)

3-12: "최신" 정책 보장을 위한 쿼리 메서드 추가 제안

현재 findByTypeIn은 다건을 반환해 “최신” 보장을 하지 않습니다. 타입별 최신 1건만 조회하는 메서드를 추가해 의미를 일치시키는 것을 권장합니다.

아래 패치를 적용해 주세요:

@@
-import java.util.List;
+import java.util.List;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
@@
 public interface JpaWeightPolicyRepository extends JpaRepository<AttendWeightPolicyEntity, Long> {
 	void deleteByTypeIn(Collection<AttendStatus> weightTypes);
 
 	List<AttendWeightPolicyEntity> findByTypeIn(Collection<AttendStatus> weightTypes);
+
+	// 각 타입별 최신(가장 큰 id) 정책만 조회
+	@Query("SELECT e FROM AttendWeightPolicyEntity e " +
+	       "WHERE e.type IN :types AND e.id IN (" +
+	       "  SELECT MAX(e2.id) FROM AttendWeightPolicyEntity e2 " +
+	       "  WHERE e2.type IN :types GROUP BY e2.type" +
+	       ")")
+	List<AttendWeightPolicyEntity> findLatestByTypeIn(@Param("types") Collection<AttendStatus> types);
 }
eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/AttendWeightPolicyRepositoryImpl.java (2)

33-36: "최신" 의미 일치: 최신만 조회하도록 호출 변경 제안

메서드명이 findLatestAttendWeight인 만큼 JPA 레벨에서도 최신 1건을 보장하는 메서드를 호출하는 편이 명확합니다.

-		return jpaRepository.findByTypeIn(attendStatuses).stream()
+		return jpaRepository.findLatestByTypeIn(attendStatuses).stream()
 				.map(this::convertToDomain)
 				.collect(Collectors.toList());

43-45: TODO 정리 제안

빌더 필수값 컴파일타임 보장은 어렵습니다. @NonNull을 빌더 필드에 부여해 빌드 시 널가드가 동작하도록 하거나, 생성자(팩토리)로 강제하는 방식을 권장합니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightCalculator.java (1)

32-35: 중복 키 안전장치 추가 권장 (toMap 병합 전략)

데이터 이상으로 동일 AttendStatus가 2건 이상 조회되면 toMap에서 예외가 납니다. 방어적 병합 전략을 넣는 것을 권장합니다. 근본적으로는 저장 계층에서 “최신 1건”만 조회되도록 하는 것이 더 좋습니다.

-		Map<AttendStatus, AttendWeightPolicyModel> policyMap =
-				existingPolicies.stream()
-						.collect(Collectors.toMap(AttendWeightPolicyModel::getType, Function.identity()));
+		Map<AttendStatus, AttendWeightPolicyModel> policyMap =
+				existingPolicies.stream()
+						.collect(
+								Collectors.toMap(
+										AttendWeightPolicyModel::getType,
+										Function.identity(),
+										(a, b) -> a // 중복 시 첫 값 유지 (예외 방지)
+								));
eeos/src/main/java/com/blackcompany/eeos/team/application/Service/TeamService.java (1)

1-1: 패키지 네이밍 컨벤션(옵션)

...application.Service 처럼 패키지에 대문자가 섞여 있어 관례(lowercase)와 다릅니다. 리팩토링 시점에 service로 정리 권장.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/rank/ProgramRankCounterRepository.java (1)

1-18: 비관적 잠금 사용 시 트랜잭션 보장 확인

PESSIMISTIC_WRITE는 호출부가 동일 트랜잭션 내에서 실행되어야 효과가 있습니다. 서비스 메서드에 @Transactional이 적용되어 있는지 확인해 주세요. 필요 시 lock timeout 힌트 추가도 고려하세요.

예: (선택) 타임아웃 힌트

@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "5000"))
eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java (1)

1-1: CI 실패: Spotless 포맷팅 위반

backend develop ci가 포맷팅 위반으로 실패합니다. 로컬에서 아래 명령을 실행해 주세요: ./gradlew :spotlessApply

eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1)

1-1: CI 실패: Spotless 포맷팅 위반

포맷팅 위반으로 CI가 실패 중입니다. ./gradlew :spotlessApply 실행해 주세요.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java (2)

33-37: 팀 존재 여부 체크의 N+1 쿼리 최적화 제안

existsById 루프는 팀 수만큼 DB 왕복이 발생합니다. 다건 조회로 한 번에 검증하는 편이 효율적입니다.

-   private void validateTeams(List<Long> teamIds) {
-       teamIds.forEach(
-               id -> {
-                   if (!teamRepository.existsById(id)) throw new NotFoundTeamException(id);
-               });
-   }
+   private void validateTeams(List<Long> teamIds) {
+       var missing = new java.util.HashSet<>(teamIds);
+       teamRepository.findAllById(teamIds).forEach(found -> missing.remove(found.getId()));
+       if (!missing.isEmpty()) {
+           // 필요 시, 전체 누락 ID를 포함하는 예외로 확장 고려
+           throw new NotFoundTeamException(missing.iterator().next());
+       }
+   }

1-1: CI 실패: Spotless 포맷팅 위반

포맷팅 위반으로 CI가 실패합니다. ./gradlew :spotlessApply 실행 부탁드립니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/usecase/GetAttendantInfoUsecase.java (2)

8-10: 불필요한 import 정리

MemberStatistics만 사용 중이라 AttendStatisticsResponse 본체 import는 불필요해 보입니다. 정리하면 가독성과 정적 분석 경고를 줄일 수 있습니다.

-import com.blackcompany.eeos.target.application.dto.AttendStatisticsResponse;
 import com.blackcompany.eeos.target.application.dto.AttendStatisticsResponse.MemberStatistics;

1-1: CI 실패: Spotless 포맷팅 위반

Spotless 위반으로 CI가 실패합니다. ./gradlew :spotlessApply 실행해 주세요.

eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java (1)

79-81: 파라미터 명 불일치(activeStatus ↔ memberStatus) 및 문서 매핑 누락

컨트롤러/서비스/유스케이스는 activeStatus를 사용합니다. 인터페이스만 memberStatus로 되어 있어 혼동을 줄 수 있습니다. 또한 기존 패턴에 맞춰 인터페이스에도 매핑을 명시하는 편이 문서 일관성에 좋습니다.

아래처럼 정리하는 것을 제안합니다:

 @Operation(summary = "전체 출석 통계 조회", description = "전체 회원의 출석 통계를 조회합니다.")
-ApiResponse<SuccessBody<PageResponse<MemberStatistics>>> getStatistics(int size, int page, String memberStatus, Long startDate, Long endDate);
+@GetMapping("/api/attend/statistic")
+ApiResponse<SuccessBody<PageResponse<MemberStatistics>>> getStatistics(
+    int size,
+    int page,
+    String activeStatus,
+    Long startDate,
+    Long endDate
+);
eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java (1)

10-17: 집계치 타입 여유 확보(선택): int → long 고려

지각/결석/벌점 합계가 커질 경우를 대비해 int 대신 long을 권장합니다. 현재 규모에서 당장 문제는 없으나 안전 측면에서 개선 포인트입니다.

-        int lateCount,
-        int absentCount,
-        int penaltyPoint
+        long lateCount,
+        long absentCount,
+        long penaltyPoint

서비스 매핑도 Long 캐스팅 후 longValue()로 맞춰야 합니다(참고: AttendService.getStatistics).

eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (2)

145-156: GET 응답에 MessageCode.CREATE 사용됨

조회 API이므로 MessageCode.GET이 맞습니다.

-        return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.CREATE);
+        return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.GET);

145-156: activeStatus 기본/전체 처리 고려(선택)

activeStatus를 선택 파라미터로 두고 기본값 "ALL"을 두면 클라이언트 사용성이 좋아집니다.

-    @RequestParam("activeStatus") String activeStatus,
+    @RequestParam(value = "activeStatus", required = false, defaultValue = "ALL") String activeStatus,
eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (1)

351-352: 메서드 시그니처 파라미터 순서 일관성(선택)

동일 서비스의 다른 메서드들은 (page, size) 순서를 사용합니다. 본 메서드는 (size, page)로 반전되어 있어 혼동 여지가 있습니다. 통일을 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13c86d6 and 367ebbb.

📒 Files selected for processing (33)
  • eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/model/TeamBuildingTargetModel.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/model/converter/TeamBuildingTargetEntityConverter.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/repository/AttendWeightPolicyRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (3 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendTeamBuildingService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightCalculator.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/QueryTeamBuildingTargetService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/usecase/GetAttendantInfoUsecase.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/converter/PresentationConverter.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/AttendWeightPolicyEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/AttendWeightPolicyRepositoryImpl.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/JpaWeightPolicyRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/presentation/PresentationEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/presentation/PresentationRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/rank/ProgramRankCounterEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/rank/ProgramRankCounterRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingInputDataEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingInputStatus.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingTargetEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingTargetRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/team/application/Service/TeamService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/event/DeletedTeamBuildingEventListener.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/service/CommandTeamBuildingTargetMemberService.java (1 hunks)
  • eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyServiceTest.java (1 hunks)
  • eeos/src/test/java/com/blackcompany/eeos/target/application/service/SelectTeamBuildingCommandTargetMemberServiceTest.java (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: backend develop ci
eeos/src/main/java/com/blackcompany/eeos/target/application/usecase/GetAttendantInfoUsecase.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.


[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java

[error] 1-1: Spotless formatting violations detected. Run './gradlew :spotlessApply' to fix code style issues in this file.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (31)
eeos/src/main/java/com/blackcompany/eeos/target/application/service/QueryTeamBuildingTargetService.java (1)

6-6: Import 경로 변경 LGTM — 스캔·잔여 import 확인 완료

eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingTargetRepository.java가 존재하며, 구 import는 코드베이스에 없습니다. 애플리케이션 진입점 eeos/src/main/java/com/blackcompany/eeos/EeosApplication.java(@SpringBootApplication, package com.blackcompany.eeos)가 루트라 기본 컴포넌트/리포지토리 스캔에 새 패키지가 포함됩니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingInputDataEntity.java (1)

1-1: 패키지 이동 LGTM.

임베디드 기본값(init) 유지로 NPE 리스크도 낮아 보입니다. 구 import 잔존 여부는 상단 코멘트의 스크립트로 함께 확인 부탁드립니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/model/converter/TeamBuildingTargetEntityConverter.java (1)

5-7: import 경로 업데이트 적절합니다.

로직 변경 없음 확인했습니다.

eeos/src/test/java/com/blackcompany/eeos/target/application/service/SelectTeamBuildingCommandTargetMemberServiceTest.java (1)

14-14: 테스트 import 경로 업데이트 확인.

리포지토리 목 주입과 검증 로직 영향 없음.

eeos/src/main/java/com/blackcompany/eeos/target/application/model/TeamBuildingTargetModel.java (1)

5-5: import 경로 업데이트 LGTM.

모델 API 변화 없음 확인했습니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendTeamBuildingService.java (1)

11-12: import 경로 업데이트 LGTM.

create/update 흐름 기존과 동일하게 동작합니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingTargetRepository.java (1)

1-1: 패키지 이동 LGTM.

Spring Data 시그니처/메서드명 변경 없음 확인했습니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingInputStatus.java (1)

1-1: 패키지 이동 LGTM.

열거형 API와 예외 매핑 변화 없음 확인했습니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/teambuilding/TeamBuildingTargetEntity.java (1)

1-1: 패키지 이동 확인 — 잔여 참조 없음
제공하신 스크립트 실행 결과, 구 패키지 import 및 @EntityScan/@EnableJpaRepositories의 basePackages 지정 모두 검색 결과가 비어 있어 잔여 참조가 없습니다.

eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/service/CommandTeamBuildingTargetMemberService.java (1)

9-10: 패키지 경로 이동 반영 LGTM — 레거시 import 잔존 여부 수동 확인 필요

자동 실행한 rg 명령이 출력 없이 종료되어 결과 확인 불가. 로컬에서 아래 명령을 실행해 이전 패키지 참조가 남아있는지 확인

rg -nP -C2 'com\.blackcompany\.eeos\.target\.persistence\.TeamBuildingTarget(Repository|Entity)\b'
eeos/src/main/java/com/blackcompany/eeos/teamBuilding/application/event/DeletedTeamBuildingEventListener.java (1)

3-3: 잔존 참조 없음 – 머지 승인
rg 검색 결과 기존 패키지 경로(com.blackcompany.eeos.target.persistence.TeamBuildingTarget*)에 대한 모든 import/참조가 제거되었습니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java (1)

1-45: Spotless 포맷팅 실패 — 로컬에서 적용 후 커밋/푸시 필요

CI가 Spotless 위반으로 실패했습니다. 검증 환경에서 ./gradlew를 찾을 수 없어 자동 적용을 확인할 수 없습니다. 로컬에서 아래를 실행한 뒤 변경사항을 커밋하고 푸시하세요.

#!/bin/bash
./gradlew :spotlessApply
./gradlew :spotlessCheck
eeos/src/main/java/com/blackcompany/eeos/target/application/repository/AttendWeightPolicyRepository.java (1)

1-1: 패키지 리팩토링 정돈 — LGTM

애플리케이션 레이어로의 리포지토리 인터페이스 이동이 명확합니다.

eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyServiceTest.java (1)

12-12: 임포트 경로 정리 — LGTM

리팩토링된 패키지에 맞춘 임포트 변경이 일관적입니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/policy/AttendWeightPolicyEntity.java (1)

1-1: 패키지 이동 — LGTM

영속성 서브패키지 정리가 잘 반영되었습니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightCalculator.java (1)

6-6: 임포트 경로 변경 — LGTM

리포지토리 위치 변경에 따른 임포트 수정이 적절합니다.

eeos/src/main/java/com/blackcompany/eeos/team/application/Service/TeamService.java (1)

4-4: 임포트 경로 변경 — LGTM

presentation 서브패키지로의 이동 반영이 적절합니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/rank/ProgramRankCounterEntity.java (1)

1-1: 패키지 이동 LGTM

rank 하위 패키지로의 정리는 타당합니다. JPA 스캔 범위(컴포넌트/엔티티 스캔, @EntityScan 등)에서 신규 패키지가 자동 포함되는지 한 번만 확인해 주세요.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java (1)

9-9: 레포지토리 패키지 경로 변경 반영 OK

import 경로 변경만으로 동작 영향은 없습니다.

eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1)

111-112: GET /api/attend/statistic ADMIN 제한 추가 적절

신규 통계 엔드포인트에 대한 권한 제어가 명확합니다. 컨트롤러 매핑 경로(단수/복수)와 정확히 일치하는지 최종 확인만 부탁드립니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/presentation/PresentationRepository.java (1)

1-1: 패키지 이동 LGTM

presentation 하위 패키지로의 네임스페이스 정리, 일관성 좋습니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/presentation/PresentationEntity.java (1)

1-1: 패키지 이동 LGTM

엔티티 자체 로직 변경 없이 패키지 정리만 이뤄졌습니다.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/converter/PresentationConverter.java (1)

3-3: import 경로 수정 OK

새 패키지로의 import 변경만 있으며, 생성 로직은 기존과 동일합니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java (1)

4-6: import 경로 변경 OK

presentation 하위 패키지 반영이 정상입니다.

eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java (2)

11-12: 임포트 추가 OK

새 DTO(MemberStatistics) 임포트 반영 적절합니다.


79-81: Spotless 포맷 위반 — 로컬에서 ./gradlew :spotlessApply 실행 필요

샌드박스에 ./gradlew가 없어 자동검증 실패했습니다. 로컬에서 실행 후 변경사항 커밋/푸시: ./gradlew :spotlessApply

대상: eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java (라인 79–81)

eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java (2)

6-18: DTO 구조 적절

중첩 레코드(MemberStatistics)로 응답 모델을 명확히 정의한 점 좋습니다.


1-18: Spotless 포맷 위반(CI 실패) — Spotless 적용 필요

샌드박스에서 ./gradlew가 없어 자동 검증 불가. 로컬 또는 CI에서 ./gradlew :spotlessApply를 실행해 포맷을 적용하고 커밋하세요. (파일: eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java)

eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (1)

145-156: Spotless 포맷 위반 — spotlessApply 실행 필요

CI에서 Spotless 포맷 검사 실패. 로컬에서 ./gradlew spotlessApply (또는 프로젝트 설정에 맞는 포맷 명령) 실행 후 변경사항 커밋하세요. 검증 시 샌드박스에 gradlew가 없어 자동 적용 불가(./gradlew: No such file or directory). 대상: eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (lines 145-156)

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (1)

350-384: Spotless 포맷 위반 — 로컬에서 포맷 적용 후 커밋 필요

샌드박스에서 ./gradlew :spotlessApply 실행을 시도했으나 gradlew가 없어 자동 적용 불가(오류: /bin/bash: ./gradlew: No such file or directory).
프로젝트 루트에서 ./gradlew :spotlessApply 또는 gradle spotlessApply 실행 후 변경사항을 커밋하고 푸시하세요.

eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java (1)

1-102: Spotless 포맷 위반 — 포맷 적용 필요 (CI 실패)
샌드박스에서 ./gradlew :spotlessApply 실행 시 './gradlew: No such file or directory' 오류로 자동 확인 불가. 로컬에서 ./gradlew :spotlessApply 실행해 포맷 적용 후 변경사항을 커밋/푸시.

Comment on lines 40 to 42
@Query(
"SELECT temp.memberId, temp.totalScore FROM (SELECT a.memberId as memberId, SUM(a.penaltyScore) as totalScore FROM AttendEntity a GROUP BY a.memberId) AS temp")
Page<Object[]> findByPenaltyPointSum(Pageable pageable);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

JPQL FROM 서브쿼리 + Page 카운트/정렬 이슈 — JPQL로 재작성하고 countQuery/ORDER BY 추가 필요

  • 현재 @query는 기본적으로 JPQL로 해석되며 FROM 절의 서브쿼리를 허용하지 않아 런타임에 실패할 수 있습니다.
  • Page<Object[]> 반환 시 count 쿼리 자동 생성이 GROUP BY/서브쿼리로 인해 실패/비효율 가능성이 높습니다.
  • ORDER BY 없이 페이징하면 페이지 경계가 불안정합니다.

아래처럼 JPQL로 평탄화 + countQuery + 안정적 ORDER BY를 추가해 주세요.

-	@Query(
-			"SELECT temp.memberId, temp.totalScore FROM (SELECT a.memberId as memberId, SUM(a.penaltyScore) as totalScore FROM AttendEntity a GROUP BY a.memberId) AS temp")
-	Page<Object[]> findByPenaltyPointSum(Pageable pageable);
+	@Query(
+			value = "SELECT a.memberId, SUM(a.penaltyScore) FROM AttendEntity a GROUP BY a.memberId ORDER BY SUM(a.penaltyScore) DESC",
+			countQuery = "SELECT COUNT(DISTINCT a.memberId) FROM AttendEntity a")
+	Page<Object[]> findByPenaltyPointSum(Pageable pageable);

참고: 동일한 문제 패턴(서브쿼리/페이징)이 있는 기간 제한 오버로드(findByPenaltyPointSum(startDate, endDate, Pageable))에도 동일한 형태로 적용해 주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files matching PenaltyPointRepository =="
rg -n --hidden --no-ignore 'PenaltyPointRepository' || true

echo
echo "== show file content =="
sed -n '1,240p' eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java || true

echo
echo "== find usages of findByPenaltyPointSum =="
rg -n --hidden --no-ignore 'findByPenaltyPointSum' || true

echo
echo "== show other repository files in same package =="
ls -1 eeos/src/main/java/com/blackcompany/eeos/target/persistence || true

Length of output: 3577


JPQL FROM 서브쿼리 사용 제거·페이징/카운트 안정화 (즉시 수정 필요)

  • 문제: eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java 내 여러 @query가 FROM 절에 서브쿼리 사용 — JPQL에서 허용되지 않아 런타임 에러 발생합니다. 대상 메서드: countByPenaltyPoint(..), countByPenaltyPointGreaterThan(..), findByPenaltyPointSum(startDate,endDate,Pageable), findByPenaltyPointSum(Pageable).
  • 조치(필수): findByPenaltyPointSum 오버로드들은 JPQL GROUP BY로 평탄화하고 value + countQuery + 안정적 ORDER BY를 추가하세요.

제안된 변경(예시)

@Query(
  value = "SELECT a.memberId, SUM(a.penaltyScore) FROM AttendEntity a GROUP BY a.memberId ORDER BY SUM(a.penaltyScore) DESC",
  countQuery = "SELECT COUNT(DISTINCT a.memberId) FROM AttendEntity a")
Page<Object[]> findByPenaltyPointSum(Pageable pageable);

@Query(
  value = "SELECT a.memberId, SUM(a.penaltyScore) FROM AttendEntity a WHERE a.createdDate >= :startDate AND a.createdDate <= :endDate GROUP BY a.memberId ORDER BY SUM(a.penaltyScore) DESC",
  countQuery = "SELECT COUNT(DISTINCT a.memberId) FROM AttendEntity a WHERE a.createdDate >= :startDate AND a.createdDate <= :endDate")
Page<Object[]> findByPenaltyPointSum(
  @Param("startDate") Timestamp startDate,
  @Param("endDate") Timestamp endDate,
  Pageable pageable);
  • countByPenaltyPoint / countByPenaltyPointGreaterThan: 현재도 FROM 서브쿼리 사용으로 동일 실패. 의도가 “행 단위(AttendEntity 레코드) 카운트”라면 서브쿼리 없이 단순 COUNT(a) JPQL로 교체하세요. 만약 의도가 “회원별 합계(totalScore) 기준으로 카운트”라면 JPQL에서 FROM 서브쿼리로 래핑할 수 없어 native query 또는 custom 리포지토리 구현으로 처리해야 합니다.

행 단위 카운트 예시:

@Query("SELECT COUNT(a) FROM AttendEntity a WHERE a.createdDate >= :startDate AND a.createdDate <= :endDate AND a.penaltyScore = :penaltyPoint")
Long countByPenaltyPoint(...);

@Query("SELECT COUNT(a) FROM AttendEntity a WHERE a.createdDate >= :startDate AND a.createdDate <= :endDate AND a.penaltyScore > :penaltyPoint")
Long countByPenaltyPointGreaterThan(...);

파일: eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java — 위 항목들 즉시 수정 필요.

🤖 Prompt for AI Agents
eeos/src/main/java/com/blackcompany/eeos/target/persistence/PenaltyPointRepository.java
lines ~40-42: the current @Query uses a FROM subquery which JPQL does not
support; replace the subquery queries with JPQL that groups AttendEntity by
memberId (e.g. SELECT a.memberId, SUM(a.penaltyScore) FROM AttendEntity a GROUP
BY a.memberId) and add an ORDER BY on SUM(a.penaltyScore) plus a corresponding
countQuery (COUNT(DISTINCT a.memberId)) so pagination works; do the same for the
date-range overload by moving the date filters into the WHERE clause; for
countByPenaltyPoint and countByPenaltyPointGreaterThan, replace the
FROM-subquery implementations with simple JPQL counting AttendEntity rows
(SELECT COUNT(a) FROM AttendEntity a WHERE ... ) unless you really need a count
of members by aggregated sum, in which case implement a native query or custom
repository method.

@rlajm1203 rlajm1203 merged commit ab037d9 into develop Sep 24, 2025
3 of 4 checks passed
@rlajm1203 rlajm1203 deleted the BM/feat/#285/attend-statistics branch September 24, 2025 13:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 367ebbb and 64a3207.

📒 Files selected for processing (9)
  • eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (3 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/usecase/GetAttendantInfoUsecase.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (2 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java
  • eeos/src/main/java/com/blackcompany/eeos/target/presentation/docs/AttendApi.java
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendWeightPolicyService.java
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/TeamPresentService.java
  • eeos/src/main/java/com/blackcompany/eeos/target/application/dto/AttendStatisticsResponse.java
  • eeos/src/main/java/com/blackcompany/eeos/target/application/usecase/GetAttendantInfoUsecase.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (7)
eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java (3)

3-3: 패키지 경로 변경 반영 LGTM

도메인 리팩터링에 따른 ActiveStatus import 경로 변경 적절합니다.


70-85: GROUP BY 누락, 소프트삭제 필터 누락, 정렬/카운트 보완 필요 (실행 오류 유발)

  • m.activeStatus를 SELECT하고도 GROUP BY에 포함되지 않아 JPQL 실행 시 오류 납니다.
  • a.isDeleted=false 조건이 없어 논리삭제 데이터가 포함됩니다.
  • Pageable Sort로 alias(late/absent/penaltyScore)를 정렬하면 엔티티 속성으로 해석되어 런타임 오류가 납니다. 쿼리 내 ORDER BY로 지정하세요.
  • 페이지 카운트 정확도를 위해 countQuery를 명시하세요.

아래처럼 수정 제안합니다(서비스는 정렬 없이 페이지만 전달해야 함):

-    @Query(
-            "SELECT"
-                    + "  m.id,"
-                    + "  m.name,"
-                    + "  m.activeStatus,"
-                    + "  SUM(CASE WHEN a.status = :late THEN 1 ELSE 0 END) as late,"
-                    + "  SUM (CASE WHEN a.status = :absent THEN 1 ELSE 0 END ) as absent,"
-                    + "  COALESCE(SUM (a.penaltyScore), 0) as penaltyScore "
-                    + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId=m.id WHERE a.createdDate >= :startDate AND a.createdDate <= :endDate "
-                    + "GROUP BY m.id, m.name")
+    @Query(
+            value = "SELECT"
+                    + "  m.id,"
+                    + "  m.name,"
+                    + "  m.activeStatus,"
+                    + "  SUM(CASE WHEN a.status = :late THEN 1 ELSE 0 END) as late,"
+                    + "  SUM(CASE WHEN a.status = :absent THEN 1 ELSE 0 END) as absent,"
+                    + "  COALESCE(SUM(a.penaltyScore), 0) as penaltyScore "
+                    + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId = m.id "
+                    + "WHERE a.isDeleted = false AND a.createdDate >= :startDate AND a.createdDate <= :endDate "
+                    + "GROUP BY m.id, m.name, m.activeStatus "
+                    + "ORDER BY penaltyScore DESC, absent DESC, late DESC",
+            countQuery = "SELECT COUNT(DISTINCT m.id) "
+                    + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId = m.id "
+                    + "WHERE a.isDeleted = false AND a.createdDate >= :startDate AND a.createdDate <= :endDate")
     Page<Object[]> getStatistics(

정렬을 리포지토리 쿼리로 옮긴 후, 서비스의 Pageable은 정렬 없이 생성(PageRequest.of(page-1, size))되었는지 확인해 주세요.


87-103: 집계 정렬은 쿼리 ORDER BY로, 카운트는 countQuery로 명시하세요

  • 현재 쿼리는 ORDER BY가 없어 페이징 시 비결정적 순서가 될 수 있습니다.
  • Pageable Sort alias 사용은 런타임 오류를 유발합니다. 쿼리 내 ORDER BY로 고정하세요.
  • countQuery를 명시하여 정확한 totalElements를 보장하세요.
-    @Query(
-            "SELECT"
+    @Query(
+            value = "SELECT"
             + "  m.id,"
             + "  m.name,"
             + "  m.activeStatus,"
             + "  SUM(CASE WHEN a.status = :late THEN 1 ELSE 0 END) as late,"
             + "  SUM (CASE WHEN a.status = :absent THEN 1 ELSE 0 END ) as absent,"
             + "  COALESCE(SUM (a.penaltyScore), 0) as penaltyScore "
-            + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId=m.id WHERE a.isDeleted=false AND a.createdDate >= :startDate AND a.createdDate <= :endDate AND m.activeStatus=:activeStatus "
-            + "GROUP BY m.id, m.name, m.activeStatus")
+            + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId=m.id "
+            + "WHERE a.isDeleted=false AND a.createdDate >= :startDate AND a.createdDate <= :endDate "
+            + "  AND m.activeStatus=:activeStatus "
+            + "GROUP BY m.id, m.name, m.activeStatus "
+            + "ORDER BY penaltyScore DESC, absent DESC, late DESC",
+            countQuery = "SELECT COUNT(DISTINCT m.id) "
+                    + "FROM AttendEntity a JOIN MemberEntity m ON a.memberId=m.id "
+                    + "WHERE a.isDeleted=false AND a.createdDate >= :startDate AND a.createdDate <= :endDate "
+                    + "  AND m.activeStatus=:activeStatus")
     Page<Object[]> getStatistics(
eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java (1)

14-14: DTO import 추가 적절

MemberStatistics import 추가 문제 없습니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (3)

21-21: DTO import 추가 LGTM


48-49: 리포지토리 패키지 경로 리팩터링 반영 LGTM


350-404: 집계 정렬 방식 오류 + 컴파일 오류(pages.get())

  • Pageable Sort에 alias(penaltyScore/absent/late)를 넘기는 것은 런타임 오류를 유발합니다. 리포지토리 쿼리의 ORDER BY로 정렬하고 여기서는 정렬 없이 페이징만 하세요.
  • Page에는 get() 메서드가 없습니다. pages.stream() 또는 pages.getContent().stream()을 사용해야 컴파일됩니다.
     public PageResponse<MemberStatistics> getStatistics(
             int page, int size, String activeStatus, Long startDate, Long endDate) {
 
-        Sort.Order penaltyScore = Sort.Order.desc("penaltyScore");
-        Sort.Order absent = Sort.Order.desc("absent");
-        Sort.Order late = Sort.Order.desc("late");
-
-        Pageable pageable = PageRequest.of(page - 1, size, Sort.by(penaltyScore, absent, late));
+        Pageable pageable = PageRequest.of(page - 1, size);
 
         Timestamp startTimestamp =
                 startDate == null
                         ? semesterPeriodProvider.getSemesterPeriod().getStartDate()
                         : new Timestamp(startDate);
         Timestamp endTimestamp =
                 endDate == null
                         ? semesterPeriodProvider.getSemesterPeriod().getEndDate()
                         : new Timestamp(endDate);
 
         Page<Object[]> pages;
 
         if (activeStatus == null || activeStatus.equalsIgnoreCase("all"))
             pages =
                     attendRepository.getStatistics(
                             startTimestamp, endTimestamp, AttendStatus.LATE, AttendStatus.ABSENT, pageable);
         else
             pages =
                     attendRepository.getStatistics(
                             startTimestamp,
                             endTimestamp,
                             AttendStatus.LATE,
                             AttendStatus.ABSENT,
                             ActiveStatus.find(activeStatus),
                             pageable);
 
-        if (pages.getTotalElements() != 0) {
+        if (pages.hasContent()) {
             List<MemberStatistics> statistics =
-                    pages
-                            .get()
-                            .map(
-                                    obj ->
-                                            new MemberStatistics(
-                                                    (Long) obj[0],
-                                                    (String) obj[1],
-                                                    ((ActiveStatus) obj[2]).getStatus(),
-                                                    ((Long) obj[3]).intValue(),
-                                                    ((Long) obj[4]).intValue(),
-                                                    ((Long) obj[5]).intValue()))
-                            .toList();
+                    pages.stream()
+                            .map(obj -> new MemberStatistics(
+                                    (Long) obj[0],
+                                    (String) obj[1],
+                                    ((ActiveStatus) obj[2]).getStatus(),
+                                    ((Long) obj[3]).intValue(),
+                                    ((Long) obj[4]).intValue(),
+                                    ((Long) obj[5]).intValue()))
+                            .toList();
 
             return new PageResponse<>(new PageImpl<>(statistics, pageable, pages.getTotalElements()));
         }
 
         return new PageResponse<>(Page.empty(PageRequest.of(page - 1, size)));
     }

리포지토리 쿼리에 ORDER BY가 반영되었는지(그리고 countQuery가 추가되었는지) 함께 확인해 주세요. 그렇지 않으면 페이징 정렬/카운트가 불안정합니다.

Comment on lines +145 to +156
@GetMapping("/attend/statistic")
public ApiResponse<SuccessBody<PageResponse<MemberStatistics>>> getStatistics(
@RequestParam("size") int size,
@RequestParam("page") int page,
@RequestParam("activeStatus") String activeStatus,
@RequestParam(value = "startDate", required = false) Long startDate,
@RequestParam(value = "endDate", required = false) Long endDate) {
PageResponse<MemberStatistics> response =
getAttendantInfoUsecase.getStatistics(page, size, activeStatus, startDate, endDate);

return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.CREATE);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

GET 엔드포인트에서 MessageCode.CREATE 사용됨

  • GET 응답 메시지는 MessageCode.GET을 사용해야 합니다.
  • activeStatus는 서비스에서 null/ALL 모두 처리하므로, 기본값을 "ALL"로 두면 UX가 좋아집니다.
-    public ApiResponse<SuccessBody<PageResponse<MemberStatistics>>> getStatistics(
+    public ApiResponse<SuccessBody<PageResponse<MemberStatistics>>> getStatistics(
             @RequestParam("size") int size,
             @RequestParam("page") int page,
-            @RequestParam("activeStatus") String activeStatus,
+            @RequestParam(value = "activeStatus", defaultValue = "ALL") String activeStatus,
             @RequestParam(value = "startDate", required = false) Long startDate,
             @RequestParam(value = "endDate", required = false) Long endDate) {
         PageResponse<MemberStatistics> response =
                 getAttendantInfoUsecase.getStatistics(page, size, activeStatus, startDate, endDate);
 
-        return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.CREATE);
+        return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.GET);
     }

보안 설정에서 /api/attend/statistic이 ADMIN으로 제한되었는지 다시 한 번 확인 부탁드립니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "=== Attempt to show AttendController.java ==="
if [ -f "eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java" ]; then
  sed -n '1,240p' eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java || true
else
  rg -n --hidden --no-ignore-vcs "class\s+AttendController" || true
fi

echo "=== Locate MessageCode enum ==="
rg -n --hidden --no-ignore-vcs "enum\s+MessageCode" || true
msgfile=$(rg -n --hidden --no-ignore-vcs "enum\s+MessageCode" -l || true)
if [ -n "$msgfile" ]; then
  echo "Found MessageCode in: $msgfile"
  sed -n '1,240p' "$msgfile" || true
fi

echo "=== Search for attend/statistic occurrences ==="
rg -n --hidden --no-ignore-vcs "/attend/statistic|attend/statistic|api/attend/statistic" || true

echo "=== Search security config (antMatchers/authorizeRequests/requestMatchers/mvcMatchers) ==="
rg -n --hidden --no-ignore-vcs "antMatchers|authorizeRequests|requestMatchers|mvcMatchers" || true

Length of output: 13256


GET 엔드포인트: MessageCode를 GET으로 수정하고 activeStatus 기본값을 "ALL"으로 설정; 보안은 ADMIN으로 제한됨

  • AttendController.getStatistics(eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java:145-156)에서 ApiResponseGenerator.success(..., MessageCode.CREATE) → MessageCode.GET으로 변경 필요.
  • 같은 메서드의 @RequestParam("activeStatus")를 @RequestParam(value = "activeStatus", defaultValue = "ALL") String activeStatus로 설정해 UX 개선 권장.
  • 보안: /api/attend/statistic은 eeos/src/main/java/com/blackcompany/eeos/config/security/SecurityFilterChainConfig.java:111에서 HttpMethod.GET에 대해 ADMIN으로 제한되어 있음(확인 완료).
🤖 Prompt for AI Agents
In
eeos/src/main/java/com/blackcompany/eeos/target/presentation/controller/AttendController.java
around lines 145-156, update the ApiResponseGenerator.success call to use
MessageCode.GET instead of MessageCode.CREATE, and change the activeStatus
request parameter to have a default value by replacing
@RequestParam("activeStatus") String activeStatus with @RequestParam(value =
"activeStatus", defaultValue = "ALL") String activeStatus; security is already
restricted to ADMIN for GET on this endpoint so no further access changes
needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능 개발 🔧refactor 코드 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants