diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRecruitmentApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRecruitmentApi.java index 2a1a5c2fb..6bff888a7 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRecruitmentApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRecruitmentApi.java @@ -20,10 +20,10 @@ public interface ClubRecruitmentApi { @Operation(summary = "동아리 모집 정보를 조회한다.", description = """ 동아리의 모집 공고 상세 정보를 조회합니다. - + - status는 모집 기간에 따라 BEFORE(모집 전), ONGOING(모집 중), CLOSED(모집 마감)으로 반환됩니다. - 동아리 멤버이거나 지원 이력이 존재할 경우 isApplied는 true로 반환됩니다. - + ## 에러 - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. @@ -39,7 +39,6 @@ ResponseEntity getRecruitments( 요청 값을 기준으로 동아리 모집 공고를 저장합니다. - 모집 공고가 없으면 생성 - 모집 공고가 있으면 수정 - ## 에러 - INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다. - INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다. diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoReplaceRequest.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoReplaceRequest.java index 91656adf8..e30c9b018 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoReplaceRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoReplaceRequest.java @@ -1,7 +1,9 @@ package gg.agit.konect.domain.club.dto; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; @@ -25,8 +27,8 @@ public record ClubFeeInfoReplaceRequest( @Schema(description = "예금주", example = "BCSD", requiredMode = NOT_REQUIRED) String accountHolder, - @NotNull(message = "회비 납부 필요 여부는 필수로 입력해야 합니다.") - @Schema(description = "회비 납부 필요 여부", example = "true", requiredMode = REQUIRED) - Boolean isFeeRequired + @Schema(description = "납부 기한", example = "2025.12.31", requiredMode = NOT_REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd") + LocalDate deadLine ) { } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoResponse.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoResponse.java index 1a8231623..fba4d81d0 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoResponse.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubFeeInfoResponse.java @@ -2,6 +2,10 @@ import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonFormat; + import gg.agit.konect.domain.club.model.Club; import io.swagger.v3.oas.annotations.media.Schema; @@ -18,8 +22,9 @@ public record ClubFeeInfoResponse( @Schema(description = "예금주", example = "BCSD", requiredMode = REQUIRED) String accountHolder, - @Schema(description = "회비 납부 필요 여부", example = "true", requiredMode = REQUIRED) - Boolean isFeeRequired + @Schema(description = "납부 기한", example = "2025.12.31", requiredMode = REQUIRED) + @JsonFormat(pattern = "yyyy.MM.dd") + LocalDate deadLine ) { public static ClubFeeInfoResponse from(Club club) { return new ClubFeeInfoResponse( @@ -27,7 +32,7 @@ public static ClubFeeInfoResponse from(Club club) { club.getFeeBank(), club.getFeeAccountNumber(), club.getFeeAccountHolder(), - club.getIsFeeRequired() + club.getFeeDeadline() ); } } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentResponse.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentResponse.java index b93d43005..6a3f68fc7 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentResponse.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentResponse.java @@ -37,6 +37,9 @@ public record ClubRecruitmentResponse( @Schema(description = "모집 공고 이미지 리스트", requiredMode = REQUIRED) List images, + @Schema(description = "회비 납부 필요 여부", example = "true", requiredMode = REQUIRED) + Boolean isFeeRequired, + @Schema(description = "지원 여부", example = "false", requiredMode = REQUIRED) Boolean isApplied ) { @@ -60,6 +63,7 @@ public static ClubRecruitmentResponse of(ClubRecruitment recruitment, Boolean is recruitment.getImages().stream() .map(InnerRecruitmentImage::from) .toList(), + recruitment.getIsFeeRequired(), isApplied ); } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java index c807a28f5..558126857 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java @@ -26,6 +26,10 @@ public record ClubRecruitmentUpsertRequest( @Schema(description = "상시 모집 여부", example = "false", requiredMode = REQUIRED) Boolean isAlwaysRecruiting, + @NotNull(message = "회비 납부 필요 여부는 필수 입력입니다.") + @Schema(description = "회비 납부 필요 여부", example = "true", requiredMode = REQUIRED) + Boolean isFeeRequired, + @NotEmpty(message = "모집 공고 내용은 필수 입력입니다.") @Schema(description = "모집 공고 내용", example = "BCSD 2025학년도 2학기 신입 부원 모집...", requiredMode = REQUIRED) String content, diff --git a/src/main/java/gg/agit/konect/domain/club/model/Club.java b/src/main/java/gg/agit/konect/domain/club/model/Club.java index c93d2f45b..769d9d2b9 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/Club.java +++ b/src/main/java/gg/agit/konect/domain/club/model/Club.java @@ -7,6 +7,8 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import java.time.LocalDate; + import org.springframework.util.StringUtils; import gg.agit.konect.domain.club.dto.ClubCreateRequest; @@ -75,8 +77,8 @@ public class Club extends BaseEntity { @Column(name = "fee_account_holder", length = 100) private String feeAccountHolder; - @Column(name = "is_fee_required", columnDefinition = "TINYINT(1)") - private Boolean isFeeRequired; + @Column(name = "fee_deadline") + private LocalDate feeDeadline; @OneToOne(mappedBy = "club", fetch = LAZY, cascade = ALL, orphanRemoval = true) private ClubRecruitment clubRecruitment; @@ -95,7 +97,7 @@ private Club( String feeBank, String feeAccountNumber, String feeAccountHolder, - Boolean isFeeRequired, + LocalDate feeDeadline, ClubRecruitment clubRecruitment ) { this.id = id; @@ -110,7 +112,7 @@ private Club( this.feeBank = feeBank; this.feeAccountNumber = feeAccountNumber; this.feeAccountHolder = feeAccountHolder; - this.isFeeRequired = isFeeRequired; + this.feeDeadline = feeDeadline; this.clubRecruitment = clubRecruitment; } @@ -131,18 +133,18 @@ public void replaceFeeInfo( String feeBank, String feeAccountNumber, String feeAccountHolder, - Boolean isFeeRequired + LocalDate feeDeadline ) { - if (isFeeInfoEmpty(feeAmount, feeBank, feeAccountNumber, feeAccountHolder)) { + if (isFeeInfoEmpty(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline)) { clearFeeInfo(); return; } - if (!isFeeInfoComplete(feeAmount, feeBank, feeAccountNumber, feeAccountHolder)) { + if (!isFeeInfoComplete(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline)) { throw CustomException.of(INVALID_REQUEST_BODY); } - updateFeeInfo(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, isFeeRequired); + updateFeeInfo(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline); } public void updateInfo(String description, String imageUrl, String location, String introduce) { @@ -161,24 +163,28 @@ private boolean isFeeInfoEmpty( Integer feeAmount, String feeBank, String feeAccountNumber, - String feeAccountHolder + String feeAccountHolder, + LocalDate feeDeadline ) { return feeAmount == null && feeBank == null && feeAccountNumber == null - && feeAccountHolder == null; + && feeAccountHolder == null + && feeDeadline == null; } private boolean isFeeInfoComplete( Integer feeAmount, String feeBank, String feeAccountNumber, - String feeAccountHolder + String feeAccountHolder, + LocalDate feeDeadline ) { return feeAmount != null && StringUtils.hasText(feeBank) && StringUtils.hasText(feeAccountNumber) - && StringUtils.hasText(feeAccountHolder); + && StringUtils.hasText(feeAccountHolder) + && feeDeadline != null; } private void updateFeeInfo( @@ -186,13 +192,13 @@ private void updateFeeInfo( String feeBank, String feeAccountNumber, String feeAccountHolder, - Boolean isFeeRequired + LocalDate feeDeadline ) { this.feeAmount = feeAmount; this.feeBank = feeBank; this.feeAccountNumber = feeAccountNumber; this.feeAccountHolder = feeAccountHolder; - this.isFeeRequired = isFeeRequired; + this.feeDeadline = feeDeadline; } private void clearFeeInfo() { @@ -200,6 +206,6 @@ private void clearFeeInfo() { this.feeBank = null; this.feeAccountNumber = null; this.feeAccountHolder = null; - this.isFeeRequired = null; + this.feeDeadline = null; } } diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubRecruitment.java b/src/main/java/gg/agit/konect/domain/club/model/ClubRecruitment.java index 22e39283f..4244fb1ad 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubRecruitment.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubRecruitment.java @@ -56,6 +56,9 @@ public class ClubRecruitment extends BaseEntity { @Column(name = "is_always_recruiting", columnDefinition = "TINYINT(1)") private Boolean isAlwaysRecruiting; + @Column(name = "is_fee_required", nullable = false, columnDefinition = "TINYINT(1)") + private Boolean isFeeRequired; + @OneToOne(fetch = LAZY) @JoinColumn(name = "club_id", nullable = false, updatable = false) private Club club; @@ -70,6 +73,7 @@ private ClubRecruitment( LocalDate endDate, String content, Boolean isAlwaysRecruiting, + Boolean isFeeRequired, Club club ) { this.id = id; @@ -77,6 +81,7 @@ private ClubRecruitment( this.endDate = endDate; this.content = content; this.isAlwaysRecruiting = isAlwaysRecruiting; + this.isFeeRequired = isFeeRequired; this.club = club; } @@ -84,6 +89,7 @@ public static ClubRecruitment of( LocalDate startDate, LocalDate endDate, Boolean isAlwaysRecruiting, + Boolean isFeeRequired, String content, Club club ) { @@ -100,6 +106,7 @@ public static ClubRecruitment of( .content(content) .club(club) .isAlwaysRecruiting(isAlwaysRecruiting) + .isFeeRequired(isFeeRequired) .build(); } @@ -129,6 +136,7 @@ public void update( LocalDate startDate, LocalDate endDate, Boolean isAlwaysRecruiting, + Boolean isFeeRequired, String content ) { if (isAlwaysRecruiting) { @@ -141,6 +149,7 @@ public void update( this.startDate = startDate; this.endDate = endDate; this.isAlwaysRecruiting = isAlwaysRecruiting; + this.isFeeRequired = isFeeRequired; this.content = content; } } diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java index f21d26782..3209c67d9 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java @@ -188,7 +188,11 @@ public ClubFeeInfoResponse applyClub(Integer clubId, Integer userId, ClubApplyRe } private void validateFeePaymentImage(Club club, String feePaymentImageUrl) { - if (Boolean.TRUE.equals(club.getIsFeeRequired()) + ClubRecruitment recruitment = clubRecruitmentRepository.findByClubId(club.getId()) + .orElse(null); + + if (recruitment != null + && Boolean.TRUE.equals(recruitment.getIsFeeRequired()) && !StringUtils.hasText(feePaymentImageUrl)) { throw CustomException.of(FEE_PAYMENT_IMAGE_REQUIRED); } @@ -337,7 +341,7 @@ public ClubFeeInfoResponse replaceFeeInfo(Integer clubId, Integer userId, ClubFe bankName, request.accountNumber(), request.accountHolder(), - request.isFeeRequired() + request.deadLine() ); return ClubFeeInfoResponse.from(club); diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.java index 4218fbf4b..4e5670404 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.java @@ -52,6 +52,7 @@ public void upsertRecruitment(Integer clubId, Integer userId, ClubRecruitmentUps request.startDate(), request.endDate(), request.isAlwaysRecruiting(), + request.isFeeRequired(), request.content(), club )); @@ -61,20 +62,14 @@ public void upsertRecruitment(Integer clubId, Integer userId, ClubRecruitmentUps request.startDate(), request.endDate(), request.isAlwaysRecruiting(), + request.isFeeRequired(), request.content() ); clubRecruitment.getImages().clear(); } - addImages(clubRecruitment, request.getImageUrls()); - - if (clubRecruitment.getId() == null) { - clubRecruitmentRepository.save(clubRecruitment); - } - } - - private void addImages(ClubRecruitment clubRecruitment, List imageUrls) { + List imageUrls = request.getImageUrls(); for (int index = 0; index < imageUrls.size(); index++) { ClubRecruitmentImage image = ClubRecruitmentImage.of( imageUrls.get(index), @@ -83,5 +78,9 @@ private void addImages(ClubRecruitment clubRecruitment, List imageUrls) ); clubRecruitment.addImage(image); } + + if (clubRecruitment.getId() == null) { + clubRecruitmentRepository.save(clubRecruitment); + } } } diff --git a/src/main/resources/db/migration/V25__drop_club_fee_deadline_column.sql b/src/main/resources/db/migration/V25__drop_club_fee_deadline_column.sql deleted file mode 100644 index 5e6e9f534..000000000 --- a/src/main/resources/db/migration/V25__drop_club_fee_deadline_column.sql +++ /dev/null @@ -1,13 +0,0 @@ --- 회비 납부 기한 컬럼 삭제 -ALTER TABLE club DROP COLUMN fee_deadline; - --- 회비 납부 필요 여부를 club 테이블로 이동 -ALTER TABLE club ADD COLUMN is_fee_required TINYINT(1) NULL; - --- 기존 데이터 마이그레이션 (club_recruitment -> club) -UPDATE club c - INNER JOIN club_recruitment cr ON c.id = cr.club_id -SET c.is_fee_required = cr.is_fee_required; - --- club_recruitment에서 is_fee_required 컬럼 삭제 -ALTER TABLE club_recruitment DROP COLUMN is_fee_required; diff --git a/src/main/resources/db/migration/V27__add_fee_deadline b/src/main/resources/db/migration/V27__add_fee_deadline new file mode 100644 index 000000000..c883b1873 --- /dev/null +++ b/src/main/resources/db/migration/V27__add_fee_deadline @@ -0,0 +1,9 @@ +ALTER TABLE club_recruitment ADD COLUMN is_fee_required TINYINT(1) NULL; + +UPDATE club_recruitment cr + INNER JOIN club c ON cr.club_id = c.id +SET cr.is_fee_required = c.is_fee_required; + +ALTER TABLE club DROP COLUMN is_fee_required; + +ALTER TABLE club ADD COLUMN fee_deadline DATE NULL;