Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -39,6 +39,8 @@ public enum ErrorStatus implements BaseErrorCode {
PARTICIPATION_NOT_TEST_COMPLETED(HttpStatus.BAD_REQUEST, "FEEDBACK40025", "테스트 완료 상태에서만 피드백을 제출할 수 있습니다."),
FEEDBACK_NOT_SUBMITTED(HttpStatus.BAD_REQUEST, "PARTICIPANT40026", "피드백이 제출되지 않았습니다."),
PARTICIPATION_ALREADY_TEST_COMPLETED(HttpStatus.BAD_REQUEST, "PARTICIPANT40027", "이미 테스트 완료 처리된 참여입니다."),
FEEDBACK_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "PARTICIPANT40028", "피드백 완료 상태가 아닙니다."),
PARTICIPATION_ALREADY_FEEDBACK_COMPLETED(HttpStatus.BAD_REQUEST, "PARTICIPANT40029", "이미 피드백 완료 처리된 참여입니다."),

// 401 Unauthorized
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "AUTH401", "인증이 필요합니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ ResponseEntity<ApiResponse<ParticipationResponse>> applyForParticipation(
@Operation(
summary = "내 신청 내역 조회",
description = """
내 참가 신청 내역을 조회합니다. status 파라미터로 필터링 가능
**ParticipationStatus (참가 신청 상태):**
- `PENDING`: 승인 대기
- `APPROVED`: 진행중
- `COMPLETED`: 지급 대기
- `REJECTED`: 거절됨
- `PAID`: 완료 (지급완료)
status를 입력하지 않으면 전체 조회
"""
내 참가 신청 내역을 조회합니다. status 파라미터로 필터링 가능

**ParticipationStatus (참가 신청 상태):**
- `PENDING`: 승인 대기
- `APPROVED`: 진행중
- `FEEDBACK_COMPLETED`: 피드백 완료
- `TEST_COMPLETED`: 테스트 완료
- `REJECTED`: 거절됨

status를 입력하지 않으면 전체 조회
"""
)
ResponseEntity<ApiResponse<Page<ParticipationSummaryResponse>>> getMyApplications(
@Parameter(description = "참가 상태 (선택사항)", required = false)
Expand Down Expand Up @@ -110,8 +110,13 @@ ResponseEntity<ApiResponse<Page<ParticipantPrivacyResponse>>> getParticipantsPri
);

@Operation(
summary = "참여자 완료 처리",
description = "특정 참여자의 테스트를 완료 처리합니다 (게시글 작성자만 가능)"
summary = "참여자 테스트 완료 처리",
description = """
모집자가 참여자의 피드백을 확인 후 최종 완료 처리합니다.
- FEEDBACK_COMPLETED(피드백 완료) 상태에서만 호출 가능
- TEST_COMPLETED(테스트 완료) 상태로 변경
- 리워드 지급 준비 완료
"""
)
ResponseEntity<ApiResponse<Void>> completeParticipant(
@Parameter(description = "참여 ID", required = true)
Expand All @@ -122,15 +127,15 @@ ResponseEntity<ApiResponse<Void>> completeParticipant(
@Operation(
summary = "게시글 참가 신청 통계",
description = """
게시글의 상태별 참가 신청자 인원 통계를 조회합니다. (작성자만 가능)

- pendingCount: 승인 대기 인원
- approvedCount: 진행중 인원
- completedCount: 완료 (지급 대기) 인원
- paidCount: 지급 완료 인원
- rejectedCount: 거절됨 인원
- totalCount: 전체 신청 인원
"""
게시글의 상태별 참가 신청자 인원 통계를 조회합니다. (작성자만 가능)

- pendingCount: 승인 대기 인원
- approvedCount: 진행중 인원
- feedbackCompletedCount: 피드백 완료 인원
- testCompletedCount: 테스트 완료 인원
- rejectedCount: 거절됨 인원
- totalCount: 전체 신청 인원
"""
)
ResponseEntity<ApiResponse<ParticipationStatisticsResponse>> getPostApplicationStatistics(
@Parameter(description = "게시글 ID", required = true)
Expand All @@ -141,22 +146,22 @@ ResponseEntity<ApiResponse<ParticipationStatisticsResponse>> getPostApplicationS
@Operation(
summary = "게시글 참여자 목록 조회",
description = """
특정 게시글의 참여자 목록 조회 (페이징, 필터링, 정렬, 검색 지원)

**필터링 가능 항목:**
- status (참여자 상태)
- `PENDING`: 승인 대기
- `APPROVED`: 진행중
- `COMPLETED`: 테스트 완료
- `PAID`: 지급 완료
- `REJECTED`: 거절됨
- null: 전체
- searchKeyword: 닉네임 또는 이메일 검색

**정렬:**
- sortDirection: `ASC` (오래된순), `DESC` (최신순, 기본값)
- 항상 신청일시(appliedAt) 기준으로 정렬
"""
특정 게시글의 참여자 목록 조회 (페이징, 필터링, 정렬, 검색 지원)

**필터링 가능 항목:**
- status (참여자 상태)
- `PENDING`: 승인 대기
- `APPROVED`: 진행중
- `FEEDBACK_COMPLETED`: 피드백 완료
- `TEST_COMPLETED`: 테스트 완료
- `REJECTED`: 거절됨
- null: 전체
- searchKeyword: 닉네임 또는 이메일 검색

**정렬:**
- sortDirection: `ASC` (오래된순), `DESC` (최신순, 기본값)
- 항상 신청일시(appliedAt) 기준으로 정렬
"""
)
ResponseEntity<ApiResponse<Page<ParticipantListResponse>>> getParticipants(
@Parameter(description = "게시글 ID", required = true)
Expand All @@ -177,13 +182,13 @@ ResponseEntity<ApiResponse<ParticipantDetailResponse>> getParticipantDetail(
);

@Operation(
summary = "참여자 테스트 완료 처리",
summary = "참여자 피드백 완료 처리",
description = """
참여자 본인이 테스트를 완료 처리합니다.
- APPROVED(진행중) 상태에서만 호출 가능
- TEST_COMPLETED(테스트 완료) 상태로 변경
- 이후 피드백을 제출해야
"""
참여자 본인이 피드백 제출을 완료 처리합니다.
- APPROVED(진행중) 상태에서만 호출 가능
- FEEDBACK_COMPLETED(피드백 완료) 상태로 변경
- 피드백 제출 후 호출해야
"""
)
ResponseEntity<ApiResponse<Void>> completeTestByParticipant(
@Parameter(description = "참가 신청 ID", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@

@Schema(description = "참가 신청 통계")
public record ParticipationStatisticsResponse(
@Schema(description = "승인 대기 인원")
@Schema(description = "승인 대기 인원", example = "5")
Long pendingCount,

@Schema(description = "진행중 인원", example = "10")
Long approvedCount,

@Schema(description = "완료 (지급 대기) 인원", example = "8")
Long completedCount,
@Schema(description = "피드백 완료 인원", example = "7")
Long feedbackCompletedCount,

@Schema(description = "지급 완료 인원", example = "15")
Long paidCount,
@Schema(description = "테스트 완료 인원", example = "8")
Long testCompletedCount,

@Schema(description = "거절됨 인원", example = "3")
Long rejectedCount,

@Schema(description = "전체 신청 인원", example = "41")
@Schema(description = "전체 신청 인원", example = "33")
Long totalCount
) {
public static ParticipationStatisticsResponse of (Long pendingCount, Long approvedCount,
Long completedCount, Long paidCount,
Long rejectedCount) {
Long total = pendingCount + approvedCount + completedCount + paidCount + rejectedCount;
public static ParticipationStatisticsResponse of(Long pendingCount, Long approvedCount,
Long feedbackCompletedCount, Long testCompletedCount,
Long rejectedCount) {
Long total = pendingCount + approvedCount + feedbackCompletedCount + testCompletedCount + rejectedCount;
return new ParticipationStatisticsResponse(
pendingCount,
approvedCount,
completedCount,
paidCount,
feedbackCompletedCount,
testCompletedCount,
rejectedCount,
total
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,23 @@ public void reject() {
this.approvedAt = LocalDateTime.now();
}

// 모집자가 테스터 피드백까지 받은 후 최종 완료 처리 상태
public void completeTest() {
if (!isApproved()) {
throw new GeneralException(ErrorStatus.PARTICIPATION_NOT_APPROVED);
}
this.status = ParticipationStatus.FEEDBACK_COMPLETED;
}

public void complete() {
this.status = ParticipationStatus.COMPLETED;
if (!isFeedbackCompleted()) {
throw new GeneralException(ErrorStatus.FEEDBACK_NOT_COMPLETED);
}
this.status = ParticipationStatus.TEST_COMPLETED;
this.completedAt = LocalDateTime.now();
}

public void updatePaidStatus(LocalDateTime paidAt) {
if (!isCompleted()) {
if (!isTestCompleted()) {
throw new GeneralException(ErrorStatus.NOT_COMPLETED_YET);
}
if (isPaid()) {
Expand All @@ -160,23 +169,15 @@ public boolean isRejected() {
return this.status == ParticipationStatus.REJECTED;
}

public boolean isCompleted() {
return this.status == ParticipationStatus.COMPLETED;
}

public boolean isPaid() {
return this.isPaid;
}

// 참여자의 테스트 참여 완료 처리 (피드백 제출)
public void completeTest() {
if (!isApproved()) {
throw new GeneralException(ErrorStatus.PARTICIPATION_NOT_APPROVED);
}
this.status = ParticipationStatus.TEST_COMPLETED;
public boolean isFeedbackCompleted() {
return this.status == ParticipationStatus.FEEDBACK_COMPLETED;
}

public boolean isTestCompleted() {
return this.status == ParticipationStatus.TEST_COMPLETED;
}

public boolean isPaid() {
return this.isPaid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ public enum ParticipationStatus {
@Schema(description = "진행중")
APPROVED("진행중"),

// 참여자의 테스트 완료 (피드백 제출)
@Schema(description = "피드백 완료")
FEEDBACK_COMPLETED("피드백 완료"),

@Schema(description = "테스트 완료")
TEST_COMPLETED("테스트 완료"),

// 모집자가 확인 후 최종 테스트 완료 및 지급 대기 상태
@Schema(description = "지급 대기")
COMPLETED("지급 대기"),

@Schema(description = "거절됨")
REJECTED("거절됨");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,8 @@ Long countByPostIdAndStatusAndIsPaid(
"SELECT " +
"COALESCE(COUNT(CASE WHEN status = 'PENDING' THEN 1 END), 0) as pendingCount, " +
"COALESCE(COUNT(CASE WHEN status = 'APPROVED' THEN 1 END), 0) as approvedCount, " +
"COALESCE(COUNT(CASE WHEN status = 'COMPLETED' AND is_paid = false THEN 1 END), 0) as completedCount, "
+
"COALESCE(COUNT(CASE WHEN status = 'COMPLETED' AND is_paid = true THEN 1 END), 0) as paidCount, " +
"COALESCE(COUNT(CASE WHEN status = 'FEEDBACK_COMPLETED' THEN 1 END), 0) as feedbackCompletedCount, " +
"COALESCE(COUNT(CASE WHEN status = 'TEST_COMPLETED' THEN 1 END), 0) as testCompletedCount, " +
"COALESCE(COUNT(CASE WHEN status = 'REJECTED' THEN 1 END), 0) as rejectedCount " +
"FROM participations " +
"WHERE post_id = :postId",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private BooleanExpression statusCondition(String status) {
}

if (PAID_STATUS.equalsIgnoreCase(status)) {
return participation.status.eq(ParticipationStatus.COMPLETED)
return participation.status.eq(ParticipationStatus.TEST_COMPLETED)
.and(participation.isPaid.isTrue());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,8 @@ public Page<ParticipationSummaryResponse> getMyApplications(Long userId, String

Page<Participation> participations;

// status가 없으면 전체 조회
if (statusParam == null || statusParam.isEmpty()) {
participations = participationRepository.findByUserIdWithPost(userId, pageable);
} else if ("PAID".equalsIgnoreCase(statusParam)) {
participations = participationRepository.findByUserIdAndStatusAndIsPaidWithPost(
userId, ParticipationStatus.COMPLETED, true, pageable);
} else {
ParticipationStatus status = ParticipationStatus.valueOf(statusParam.toUpperCase());
participations = participationRepository.findByUserIdAndStatusWithPost(userId, status, pageable);
Expand Down Expand Up @@ -196,7 +192,7 @@ public void completeByParticipant(Long participationId, Long userId) {
Participation participation = getParticipation(participationId);

validateParticipantOwnership(participation, userId);
validateNotAlreadyTestCompleted(participation);
validateNotAlreadyFeedbackCompleted(participation);

participation.completeTest();
}
Expand Down Expand Up @@ -251,8 +247,8 @@ public ParticipationStatisticsResponse getPostApplicationStatistics(Long postId,
return ParticipationStatisticsResponse.of(
stats.pendingCount(),
stats.approvedCount(),
stats.completedCount(),
stats.paidCount(),
stats.feedbackCompletedCount(),
stats.testCompletedCount(),
stats.rejectedCount()
);
}
Expand Down Expand Up @@ -390,15 +386,21 @@ private ParticipationStatsDto extractParticipationStats(Long postId) {
Object[] result = resultList.get(0);

return new ParticipationStatsDto(
extractLongValue(result[0]),
extractLongValue(result[1]),
extractLongValue(result[2]),
extractLongValue(result[3]),
extractLongValue(result[4])
extractLongValue(result[0]), // pendingCount
extractLongValue(result[1]), // approvedCount
extractLongValue(result[2]), // feedbackCompletedCount
extractLongValue(result[3]), // testCompletedCount
extractLongValue(result[4]) // rejectedCount
);
}

private Long extractLongValue(Object value) {
return value != null ? ((Number) value).longValue() : 0L;
}

private void validateNotAlreadyFeedbackCompleted(Participation participation) {
if (participation.isFeedbackCompleted() || participation.isTestCompleted()) {
throw new GeneralException(ErrorStatus.PARTICIPATION_ALREADY_FEEDBACK_COMPLETED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
public record ParticipationStatsDto(
Long pendingCount,
Long approvedCount,
Long completedCount,
Long paidCount,
Long feedbackCompletedCount,
Long testCompletedCount,
Long rejectedCount
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static ParticipantReward create(Participation participation, PostReward p
}

public void markAsCompleted() {
this.completionStatus = ParticipationStatus.COMPLETED;
this.completionStatus = ParticipationStatus.TEST_COMPLETED;
this.completedAt = LocalDateTime.now();
this.rewardStatus = RewardStatus.PENDING;
}
Expand All @@ -80,7 +80,7 @@ public void markAsPaid() {
}

public boolean isCompleted() {
return this.completionStatus == ParticipationStatus.COMPLETED;
return this.completionStatus == ParticipationStatus.TEST_COMPLETED;
}

public boolean isRewardPaid() {
Expand Down