From 9d8c613d2d1b0c1d7b14d56cf2812d702a7a062a Mon Sep 17 00:00:00 2001 From: BonSik Date: Tue, 20 Feb 2024 18:06:53 +0900 Subject: [PATCH 01/29] =?UTF-8?q?rename:=20ScheduleParticipant=20Service?= =?UTF-8?q?=20Layer=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20UseCase?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../volunteer/domain/logboard/api/LogboardController.java | 4 ++-- .../domain/recruitment/application/RecruitmentFacade.java | 4 ++-- .../mapper/ScheduleParticipantFacade.java | 8 ++++---- ...Impl.java => ScheduleParticipationCommandService.java} | 2 +- ...vice.java => ScheduleParticipationCommandUseCase.java} | 2 +- ...ceImpl.java => ScheduleParticipationQueryService.java} | 2 +- ...ervice.java => ScheduleParticipationQueryUseCase.java} | 2 +- .../sehedule/application/ScheduleCommandFacade.java | 4 ++-- .../api/ScheduleParticipantControllerTest.java | 5 +++-- .../service/ScheduleParticipationDtoServiceImplTest.java | 3 ++- .../service/ScheduleParticipationServiceImplTest.java | 3 ++- 11 files changed, 21 insertions(+), 18 deletions(-) rename src/main/java/project/volunteer/domain/scheduleParticipation/service/{ScheduleParticipationServiceImpl.java => ScheduleParticipationCommandService.java} (98%) rename src/main/java/project/volunteer/domain/scheduleParticipation/service/{ScheduleParticipationService.java => ScheduleParticipationCommandUseCase.java} (92%) rename src/main/java/project/volunteer/domain/scheduleParticipation/service/{ScheduleParticipationDtoServiceImpl.java => ScheduleParticipationQueryService.java} (97%) rename src/main/java/project/volunteer/domain/scheduleParticipation/service/{ScheduleParticipationDtoService.java => ScheduleParticipationQueryUseCase.java} (96%) diff --git a/src/main/java/project/volunteer/domain/logboard/api/LogboardController.java b/src/main/java/project/volunteer/domain/logboard/api/LogboardController.java index 84aed13e..19d33aa8 100644 --- a/src/main/java/project/volunteer/domain/logboard/api/LogboardController.java +++ b/src/main/java/project/volunteer/domain/logboard/api/LogboardController.java @@ -45,7 +45,7 @@ import project.volunteer.domain.logboard.dao.LogboardRepository; import project.volunteer.domain.logboard.dao.dto.LogboardListQuery; import project.volunteer.domain.reply.application.ReplyService; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationDtoService; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryUseCase; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.image.domain.Storage; import project.volunteer.global.Interceptor.OrganizationAuth; @@ -62,7 +62,7 @@ public class LogboardController { private final FileService fileService; private final ImageRepository imageRepository; private final LogboardRepository logboardRepository; - private final ScheduleParticipationDtoService spDtoService ; + private final ScheduleParticipationQueryUseCase spDtoService ; private final ReplyService replyService; private final ReplyRepository replyRepository; private final UserService userService; diff --git a/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java b/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java index ce1df9a4..30b7363e 100644 --- a/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java +++ b/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java @@ -8,7 +8,7 @@ import project.volunteer.domain.notice.application.NoticeService; import project.volunteer.domain.recruitmentParticipation.application.RecruitmentParticipationUseCase; import project.volunteer.domain.recruitment.api.dto.request.RecruitmentRequest; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationService; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; import project.volunteer.domain.sehedule.application.ScheduleCommandUseCase; import project.volunteer.domain.user.application.UserService; import project.volunteer.domain.user.domain.User; @@ -30,7 +30,7 @@ public class RecruitmentFacade { private final ImageService imageService; - private final ScheduleParticipationService scheduleParticipationService; + private final ScheduleParticipationCommandUseCase scheduleParticipationService; private final NoticeService noticeService; private final ConfirmationService confirmationService; diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java index 37065479..d240a471 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java @@ -5,8 +5,8 @@ import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.recruitmentParticipation.application.RecruitmentParticipationUseCase; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationDtoService; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationService; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryUseCase; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; @@ -21,8 +21,8 @@ public class ScheduleParticipantFacade { private final ScheduleQueryUseCase scheduleQueryUsecase; private final RecruitmentParticipationUseCase recruitmentParticipationUseCase; - private final ScheduleParticipationService scheduleParticipationService; - private final ScheduleParticipationDtoService scheduleParticipationDtoService; + private final ScheduleParticipationCommandUseCase scheduleParticipationService; + private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; @Transactional public void participateVolunteerPostSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImpl.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java similarity index 98% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImpl.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index f5c3a8de..cd19d589 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImpl.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -16,7 +16,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class ScheduleParticipationServiceImpl implements ScheduleParticipationService { +public class ScheduleParticipationCommandService implements ScheduleParticipationCommandUseCase { private final ScheduleParticipationRepository scheduleParticipationRepository; @Override diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java similarity index 92% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationService.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index 4f4c280d..c308b0c1 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -5,7 +5,7 @@ import java.util.List; -public interface ScheduleParticipationService { +public interface ScheduleParticipationCommandUseCase { public void participate(Schedule schedule, RecruitmentParticipation participant); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImpl.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java similarity index 97% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImpl.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java index 848b6ef0..39a2a7a4 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImpl.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java @@ -20,7 +20,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class ScheduleParticipationDtoServiceImpl implements ScheduleParticipationDtoService{ +public class ScheduleParticipationQueryService implements ScheduleParticipationQueryUseCase { private final ScheduleParticipationRepository scheduleParticipationRepository; @Override diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java similarity index 96% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoService.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java index 47172ae2..36243829 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java @@ -10,7 +10,7 @@ import java.util.List; -public interface ScheduleParticipationDtoService { +public interface ScheduleParticipationQueryUseCase { //봉사 일정 참여 상태 조회 Optional searchState(Long scheduleNo, Long userNo); diff --git a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java index 4ed3a516..8e3018f4 100644 --- a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java +++ b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java @@ -5,7 +5,7 @@ import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.recruitment.application.RecruitmentQueryUseCase; import project.volunteer.domain.recruitment.domain.Recruitment; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationService; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; import project.volunteer.domain.sehedule.application.dto.command.ScheduleUpsertCommand; @Service @@ -14,7 +14,7 @@ public class ScheduleCommandFacade { private final RecruitmentQueryUseCase recruitmentQueryUseCase; private final ScheduleCommandUseCase scheduleCommandService; - private final ScheduleParticipationService scheduleParticipationService; + private final ScheduleParticipationCommandUseCase scheduleParticipationService; public Long registerSchedule(Long recruitmentNo, ScheduleUpsertCommand command) { Recruitment recruitment = recruitmentQueryUseCase.findActivatedRecruitment(recruitmentNo); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java index df31113e..8bddf805 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java @@ -34,7 +34,7 @@ import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationService; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; import project.volunteer.domain.sehedule.repository.ScheduleRepository; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.image.domain.Storage; @@ -79,7 +79,8 @@ class ScheduleParticipantControllerTest { @Autowired RecruitmentParticipationRepository participantRepository; @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired ScheduleParticipationService spService; + @Autowired + ScheduleParticipationCommandUseCase spService; @Autowired ImageService imageService; @Autowired ImageRepository imageRepository; @Autowired FileService fileService; diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java index 7fe6729f..33207638 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java @@ -50,7 +50,8 @@ class ScheduleParticipationDtoServiceImplTest { RecruitmentParticipationRepository participantRepository; @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired ScheduleParticipationDtoService spDtoService; + @Autowired + ScheduleParticipationQueryUseCase spDtoService; private User writer; private Recruitment saveRecruitment; diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java index 609b6fe8..2fbd5ee0 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java @@ -45,7 +45,8 @@ class ScheduleParticipationServiceImplTest { @Autowired RecruitmentParticipationRepository participantRepository; @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired ScheduleParticipationService spService; + @Autowired + ScheduleParticipationCommandUseCase spService; private User writer; private Recruitment saveRecruitment; From de7a3ea72b26a6e574e5cd1dba0ceb21e175671b Mon Sep 17 00:00:00 2001 From: BonSik Date: Tue, 20 Feb 2024 20:01:32 +0900 Subject: [PATCH 02/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20UseCase=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=82=B4=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?/=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ScheduleParticipation.java | 44 ++++-- .../mapper/ScheduleParticipantFacade.java | 7 + .../ScheduleParticipationRepository.java | 41 +++-- .../ParticipationServiceConcurrent.java | 14 +- .../ScheduleParticipationCommandService.java | 103 +++++++----- .../ScheduleParticipationCommandUseCase.java | 6 +- .../domain/sehedule/domain/Schedule.java | 38 +++-- .../user/application/UserDtoServiceImpl.java | 2 +- .../queryDto/UserQueryDtoRepositoryImpl.java | 14 +- .../global/common/component/Timetable.java | 11 ++ .../global/error/exception/ErrorCode.java | 6 +- src/main/resources/messages.properties | 6 +- .../ScheduleParticipantControllerTest.java | 10 +- .../domain/ScheduleParticipationTest.java | 72 +++++++++ .../ScheduleParticipationRepositoryTest.java | 2 +- ...heduleParticipationCommandUseCaseTest.java | 146 ++++++++++++++++++ .../ScheduleParticipationServiceImplTest.java | 114 +------------- .../domain/sehedule/domain/ScheduleTest.java | 2 +- .../volunteer/support/ServiceTest.java | 4 + 19 files changed, 433 insertions(+), 209 deletions(-) create mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipationTest.java create mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java index 05824f69..5cdb3869 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java @@ -20,30 +20,57 @@ public class ScheduleParticipation extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "schedule_participant_no") - private Long scheduleParticipationNo; + private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "scheduleno") private Schedule schedule; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "participantno") - private RecruitmentParticipation participant; + @JoinColumn(name = "recruitment_participation_no") + private RecruitmentParticipation recruitmentParticipation; @Convert(converter = StateConverter.class) @Column(length = 3, nullable = false) private ParticipantState state; - public ScheduleParticipation(Schedule schedule, RecruitmentParticipation participant, ParticipantState state) { + public ScheduleParticipation(Schedule schedule, RecruitmentParticipation recruitmentParticipation, ParticipantState state) { this.schedule = schedule; - this.participant = participant; + this.recruitmentParticipation = recruitmentParticipation; this.state = state; } - public static ScheduleParticipation createScheduleParticipation(Schedule schedule, RecruitmentParticipation participant, ParticipantState state){ + public Boolean canReParticipation() { + return state.equals(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); + } + + public void changeState(ParticipantState state) { + this.state = state; + } + + @Override + public String toString() { + return "ScheduleParticipation{" + + "scheduleParticipationNo=" + id + + ", scheduleNo=" + schedule.getScheduleNo() + + ", recruitmentParticipationNo=" + recruitmentParticipation.getId() + + ", state=" + state + + '}'; + } + + + + + + + + + + + public static ScheduleParticipation createScheduleParticipation(Schedule schedule, RecruitmentParticipation recruitmentParticipation, ParticipantState state){ ScheduleParticipation createScheduleParticipation = new ScheduleParticipation(); createScheduleParticipation.schedule = schedule; - createScheduleParticipation.participant = participant; + createScheduleParticipation.recruitmentParticipation = recruitmentParticipation; createScheduleParticipation.state = state; return createScheduleParticipation; } @@ -53,9 +80,8 @@ public void delete(){ } public void removeScheduleAndParticipant(){ this.schedule = null; - this.participant = null; + this.recruitmentParticipation = null; } - public void updateState(ParticipantState state) {this.state = state;} public Boolean isEqualState(ParticipantState state) {return this.state.equals(state);} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java index d240a471..82369c25 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java @@ -33,6 +33,13 @@ public void participateVolunteerPostSchedule(Long userNo, Long recruitmentNo, Lo scheduleParticipationService.participate(schedule, recruitmentParticipation); } + + + + + + + @Transactional public void cancelParticipationVolunteerPostSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java index e0da7fe1..e796e7ac 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java @@ -17,21 +17,30 @@ public interface ScheduleParticipationRepository extends JpaRepository, ScheduleParticipationQueryDSLRepository { + Boolean existsByScheduleAndRecruitmentParticipation(Schedule schedule, RecruitmentParticipation recruitmentParticipation); + + Optional findByScheduleAndRecruitmentParticipation(Schedule schedule, RecruitmentParticipation participant); + + + + + + + + @Query("select sp " + "from ScheduleParticipation sp " + - "join sp.participant p on p.user.userNo=:userNo " + + "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + "and sp.schedule.scheduleNo=:scheduleNo") Optional findByUserNoAndScheduleNo(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo); @Query("select sp.state " + "from ScheduleParticipation sp " + - "join sp.participant p on p.user.userNo=:userNo " + + "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + "and sp.schedule.scheduleNo=:scheduleNo") Optional findStateBy(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo); - Optional findByScheduleAndParticipant(Schedule schedule, RecruitmentParticipation participant); - //일정 참여 중인 인원 수 반환 쿼리 @Query("select count(sp) " + "from ScheduleParticipation sp " + @@ -39,8 +48,6 @@ Optional findByUserNoAndScheduleNo(@Param("userNo") Long "and sp.state=project.volunteer.global.common.component.ParticipantState.PARTICIPATING") Integer countActiveParticipant(@Param("scheduleNo") Long scheduleNo); - List findBySchedule_ScheduleNoAndState(Long scheduleNo, ParticipantState state); - List findBySchedule_ScheduleNo(Long scheduleNo); @Query("select sp " + @@ -52,26 +59,26 @@ Optional findByUserNoAndScheduleNo(@Param("userNo") Long @Query("select sp " + "from ScheduleParticipation sp " + - "join sp.participant p on p.user.userNo=:userNo " + + "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + "where sp.schedule.scheduleNo=:scheduleNo " + "and sp.state=:state ") Optional findByUserNoAndScheduleNoAndState(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo, @Param("state") ParticipantState state); - Optional findByScheduleAndParticipantAndState(Schedule schedule, RecruitmentParticipation participant, - ParticipantState state); + Optional findByScheduleAndRecruitmentParticipationAndState(Schedule schedule, RecruitmentParticipation participant, + ParticipantState state); - Optional findByScheduleParticipationNoAndState(Long scheduleParticipationNo, - ParticipantState state); + Optional findByIdAndState(Long scheduleParticipationNo, + ParticipantState state); - List findByScheduleParticipationNoIn(List spNos); + List findByIdIn(List spNos); //N+1 문제를 막기 위해서 Projection + Join 방식 사용 - @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails(sp.scheduleParticipationNo,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + "from ScheduleParticipation sp " + - "join sp.participant p " + + "join sp.recruitmentParticipation p " + "join p.user u " + "left join Image i " + "on i.no=u.userNo " + @@ -85,17 +92,17 @@ List findOptimizationParticipantByScheduleAndState(@Param("s @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.CompletedScheduleDetail" + "(sp.schedule.scheduleNo " + - ",sp.participant.recruitment.title " + + ",sp.recruitmentParticipation.recruitment.title " + ",sp.schedule.scheduleTimeTable.endDay) " + "from ScheduleParticipation sp " + - "where sp.participant.user.userNo=:loginUserNo " + + "where sp.recruitmentParticipation.user.userNo=:loginUserNo " + "and sp.state=:state") List findCompletedSchedules(@Param("loginUserNo") Long loginUserNo, @Param("state") ParticipantState state); @Query("select sp " + "from ScheduleParticipation sp " + - "where sp.participant.user.userNo=:loginUserNo " + + "where sp.recruitmentParticipation.user.userNo=:loginUserNo " + "and sp.state=:state") List findScheduleListByUsernoAndStatus(@Param("loginUserNo") Long loginUserNo, @Param("state") ParticipantState state); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java index de4dff23..98a31f8e 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java @@ -52,7 +52,7 @@ public void participateWithoutLock(Long recruitmentNo, Long scheduleNo, Long log } //재신청 - sp.updateState(ParticipantState.PARTICIPATING); + sp.changeState(ParticipantState.PARTICIPATING); }, () ->{ //신규 신청 @@ -73,7 +73,7 @@ public void participateWithOPTIMSTICLock(Long recruitmentNo, Long scheduleNo, Lo //일정 검증(존재 여부, 모집 기간) Schedule findSchedule = isActiveScheduleWithOPTIMSTICLock(scheduleNo); - if(findSchedule.isFullParticipant()) { + if(findSchedule.isFull()) { throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, String.format("ScheduleNo = [%d], Active participant num = [%d]", findSchedule.getScheduleNo(), findSchedule.getCurrentVolunteerNum())); } @@ -87,7 +87,7 @@ public void participateWithOPTIMSTICLock(Long recruitmentNo, Long scheduleNo, Lo String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", findSchedule.getScheduleNo(), loginUserNo, sp.getState().name())); } //재신청 - sp.updateState(ParticipantState.PARTICIPATING); + sp.changeState(ParticipantState.PARTICIPATING); }, () -> { //신규 신청 @@ -101,7 +101,7 @@ public void participateWithOPTIMSTICLock(Long recruitmentNo, Long scheduleNo, Lo scheduleParticipationRepository.save(createSP); }); - findSchedule.increaseParticipant(); + findSchedule.increaseParticipationNum(1); } @Transactional @@ -109,7 +109,7 @@ public void participateWithPERSSIMITIC_WRITE_Lock(Long recruitmentNo, Long sched //일정 검증(존재 여부, 모집 기간) Schedule findSchedule = isActiveScheduleWithPERSSIMITIC_WRITE_Lock(scheduleNo); - if(findSchedule.isFullParticipant()){ + if(findSchedule.isFull()){ throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, String.format("ScheduleNo = [%d], Active participant num = [%d]", findSchedule.getScheduleNo(), findSchedule.getCurrentVolunteerNum())); } @@ -123,7 +123,7 @@ public void participateWithPERSSIMITIC_WRITE_Lock(Long recruitmentNo, Long sched String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", findSchedule.getScheduleNo(), loginUserNo, sp.getState().name())); } //재신청 - sp.updateState(ParticipantState.PARTICIPATING); + sp.changeState(ParticipantState.PARTICIPATING); }, () -> { //신규 신청 @@ -137,7 +137,7 @@ public void participateWithPERSSIMITIC_WRITE_Lock(Long recruitmentNo, Long sched scheduleParticipationRepository.save(createSP); }); - findSchedule.increaseParticipant(); + findSchedule.increaseParticipationNum(1); } private Schedule isActiveScheduleWithoutLock(Long scheduleNo){ diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index cd19d589..3dddc2ad 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -14,82 +14,105 @@ import java.util.List; @Service -@Transactional(readOnly = true) +@Transactional @RequiredArgsConstructor public class ScheduleParticipationCommandService implements ScheduleParticipationCommandUseCase { private final ScheduleParticipationRepository scheduleParticipationRepository; @Override - @Transactional - public void participate(Schedule schedule, RecruitmentParticipation participant) { - //모집 인원 검증 - if(schedule.isFullParticipant()){ - throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, - String.format("ScheduleNo = [%d], Active participant num = [%d]", schedule.getScheduleNo(), schedule.getCurrentVolunteerNum())); + public Long participate(final Schedule schedule, final RecruitmentParticipation recruitmentParticipation) { + checkIsFull(schedule); + + if (!scheduleParticipationRepository.existsByScheduleAndRecruitmentParticipation(schedule, + recruitmentParticipation)) { + ScheduleParticipation newScheduleParticipation = new ScheduleParticipation(schedule, + recruitmentParticipation, ParticipantState.PARTICIPATING); + schedule.increaseParticipationNum(1); + return scheduleParticipationRepository.save(newScheduleParticipation).getId(); + } + + ScheduleParticipation scheduleParticipation = findScheduleParticipation(schedule, recruitmentParticipation); + checkDuplicationParticipation(scheduleParticipation); + scheduleParticipation.changeState(ParticipantState.PARTICIPATING); + schedule.increaseParticipationNum(1); + return scheduleParticipation.getId(); + } + + private void checkIsFull(final Schedule schedule) { + if (schedule.isFull()) { + throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, schedule.toString()); + } + } + + private void checkDuplicationParticipation(final ScheduleParticipation scheduleParticipation) { + if (!scheduleParticipation.canReParticipation()) { + throw new BusinessException(ErrorCode.DUPLICATE_SCHEDULE_PARTICIPATION, scheduleParticipation.toString()); } + } - scheduleParticipationRepository.findByScheduleAndParticipant(schedule, participant) - .ifPresentOrElse( - sp -> { - //중복 신청 검증(일정 참여중, 일정 참여 취소 요청) - if(sp.isEqualState(ParticipantState.PARTICIPATING) || sp.isEqualState(ParticipantState.PARTICIPATION_CANCEL)){ - throw new BusinessException(ErrorCode.DUPLICATE_RECRUITMENT_PARTICIPATION, - String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", schedule.getScheduleNo(), participant.getUser().getUserNo(), sp.getState().name())); - } - - //재신청 - sp.updateState(ParticipantState.PARTICIPATING); - }, - () ->{ - //신규 신청 - ScheduleParticipation createSP = - ScheduleParticipation.createScheduleParticipation(schedule, participant, ParticipantState.PARTICIPATING); - scheduleParticipationRepository.save(createSP); - } - ); - //일정 참여 인원수 증가 - schedule.increaseParticipant(); + private ScheduleParticipation findScheduleParticipation(final Schedule schedule, + final RecruitmentParticipation recruitmentParticipation) { + return scheduleParticipationRepository.findByScheduleAndRecruitmentParticipation(schedule, + recruitmentParticipation) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE_PARTICIPATION)); } + + + + + + + + + + + + + + + @Override - @Transactional public void cancel(Schedule schedule, RecruitmentParticipation participant) { //일정 참여 중 상태인지 검증 - ScheduleParticipation findSp = scheduleParticipationRepository.findByScheduleAndParticipantAndState(schedule, participant, ParticipantState.PARTICIPATING) + ScheduleParticipation findSp = scheduleParticipationRepository.findByScheduleAndRecruitmentParticipationAndState( + schedule, + participant, ParticipantState.PARTICIPATING) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_STATE, - String.format("UserNo = [%d], ScheduleNo = [%d]", participant.getUser().getUserNo(), schedule.getScheduleNo()))); + String.format("UserNo = [%d], ScheduleNo = [%d]", participant.getUser().getUserNo(), + schedule.getScheduleNo()))); //일정 신청 취소 요청 - findSp.updateState(ParticipantState.PARTICIPATION_CANCEL); + findSp.changeState(ParticipantState.PARTICIPATION_CANCEL); } @Override - @Transactional public void approvalCancellation(Schedule schedule, Long spNo) { //일정 취소 요청 상태인지 검증 - ScheduleParticipation findSp = scheduleParticipationRepository.findByScheduleParticipationNoAndState(spNo, ParticipantState.PARTICIPATION_CANCEL) + ScheduleParticipation findSp = scheduleParticipationRepository.findByIdAndState(spNo, + ParticipantState.PARTICIPATION_CANCEL) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_STATE, String.format("ScheduleParticipationNo = [%d]", spNo))); //일정 취소 요청 승인 - findSp.updateState(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); + findSp.changeState(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); //일정 참가자 수 감소 schedule.decreaseParticipant(); } @Override - @Transactional public void approvalCompletion(List spNo) { - scheduleParticipationRepository.findByScheduleParticipationNoIn(spNo).stream() + scheduleParticipationRepository.findByIdIn(spNo).stream() .forEach(sp -> { //일정 참여 완료 미승인 상태가 아닌 경우 - if(!sp.isEqualState(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)){ + if (!sp.isEqualState(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)) { throw new BusinessException(ErrorCode.INVALID_STATE, - String.format("ScheduleParticipationNo = [%d], State = [%s]", sp.getScheduleParticipationNo(), sp.getState().name())); + String.format("ScheduleParticipationNo = [%d], State = [%s]", + sp.getId(), sp.getState().name())); } //일정 참여 완료 승인 - sp.updateState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); + sp.changeState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); }); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index c308b0c1..a1e5a1bc 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -7,7 +7,11 @@ public interface ScheduleParticipationCommandUseCase { - public void participate(Schedule schedule, RecruitmentParticipation participant); + Long participate(Schedule schedule, RecruitmentParticipation participant); + + + + public void cancel(Schedule schedule, RecruitmentParticipation participant); diff --git a/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java b/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java index 7f68f803..05135bd4 100644 --- a/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java +++ b/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java @@ -24,6 +24,7 @@ public class Schedule extends BaseTimeEntity { private static final int MAX_CONTENT_SIZE = 50; private static final int MAX_PARTICIPATION_NUM = 9999; private static final int MIN_PARTICIPATION_NUM = 1; + private static final int MIN_CURRENT_PARTICIPATION_NUM = 0; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -66,6 +67,7 @@ public Schedule(Timetable timetable, String content, String organizationName, Ad validateContentSize(content); validateOrganizationNameSize(organizationName); validateParticipationNum(participationNum, recruitment); + validateCurrentParticipationNum(participationNum, currentVolunteerNum); this.scheduleTimeTable = timetable; this.content = content; @@ -100,7 +102,7 @@ public void change(Recruitment recruitment, Timetable timetable, String content, validateContentSize(content); validateOrganizationNameSize(organizationName); validateParticipationNum(participationNum, recruitment); - validateCurrentParticipationNum(participationNum); + validateCurrentParticipationNum(participationNum, this.currentVolunteerNum); this.scheduleTimeTable = timetable; this.content = content; @@ -113,23 +115,23 @@ public void delete() { this.isDeleted = IsDeleted.Y; } - public void increaseParticipant() { - this.currentVolunteerNum++; + public void increaseParticipationNum(int addParticipationNum) { + this.currentVolunteerNum += addParticipationNum; + validateCurrentParticipationNum(this.volunteerNum, this.currentVolunteerNum); } public void decreaseParticipant() { this.currentVolunteerNum--; } - public Boolean isFullParticipant() { - return this.currentVolunteerNum == this.volunteerNum; + public Boolean isFull() { + return this.currentVolunteerNum.equals(this.volunteerNum); } public Boolean isAvailableDate() { return this.scheduleTimeTable.getStartDay().isAfter(LocalDate.now()); } - /** * 검증 메서드 **/ @@ -160,11 +162,10 @@ private void validateParticipationNum(final int participationNum, final Recruitm } } - private void validateCurrentParticipationNum(final int participationNum) { - if (currentVolunteerNum > participationNum) { - throw new BusinessException(ErrorCode.LESS_PARTICIPATION_NUM_THAN_CURRENT_PARTICIPANT, - String.format("currentParticipationNum = [%d], requestParticipationNum = [%d]", currentVolunteerNum, - participationNum)); + private void validateCurrentParticipationNum(final int participationNum, final int currentParticipationNum) { + if (MIN_CURRENT_PARTICIPATION_NUM > currentParticipationNum || currentParticipationNum > participationNum) { + throw new BusinessException(ErrorCode.INVALID_CURRENT_PARTICIPATION_NUM, + String.format("[%d]~[%d]", MIN_CURRENT_PARTICIPATION_NUM, participationNum)); } } @@ -174,4 +175,19 @@ public void checkDoneDate(LocalDate currentDate) { } } + @Override + public String toString() { + return "Schedule{" + + "scheduleNo=" + scheduleNo + + ", scheduleTimeTable=" + scheduleTimeTable + + ", organizationName='" + organizationName + '\'' + + ", address=" + address + + ", content='" + content + '\'' + + ", volunteerNum=" + volunteerNum + + ", currentVolunteerNum=" + currentVolunteerNum + + ", isDeleted=" + isDeleted + + ", recruitmentNo=" + recruitment.getRecruitmentNo() + + '}'; + } + } diff --git a/src/main/java/project/volunteer/domain/user/application/UserDtoServiceImpl.java b/src/main/java/project/volunteer/domain/user/application/UserDtoServiceImpl.java index 9ada255c..46122698 100644 --- a/src/main/java/project/volunteer/domain/user/application/UserDtoServiceImpl.java +++ b/src/main/java/project/volunteer/domain/user/application/UserDtoServiceImpl.java @@ -90,7 +90,7 @@ public JoinScheduleListResponse findUserSchedule(Long loginUserNo) { List scheduleParticipationList = scheduleParticipationRepository.findScheduleListByUsernoAndStatus(loginUserNo, ParticipantState.PARTICIPATING); return new JoinScheduleListResponse(scheduleParticipationList.stream().map(dto->{ - return new JoinScheduleList(dto.getScheduleParticipationNo() + return new JoinScheduleList(dto.getId() , dto.getSchedule().getScheduleTimeTable().getStartDay().format(DateTimeFormatter.ofPattern("MM-dd-yyyy")) , dto.getSchedule().getAddress().getSido() , dto.getSchedule().getAddress().getSigungu() diff --git a/src/main/java/project/volunteer/domain/user/dao/queryDto/UserQueryDtoRepositoryImpl.java b/src/main/java/project/volunteer/domain/user/dao/queryDto/UserQueryDtoRepositoryImpl.java index b001874f..0ca7da24 100644 --- a/src/main/java/project/volunteer/domain/user/dao/queryDto/UserQueryDtoRepositoryImpl.java +++ b/src/main/java/project/volunteer/domain/user/dao/queryDto/UserQueryDtoRepositoryImpl.java @@ -86,11 +86,11 @@ public Slice findHistoryDtos(Long loginUserNo, Pageable pageab List results = jpaQueryFactory .select(new QUserHistoryQuery( - scheduleParticipation.scheduleParticipationNo, storage.imagePath, - scheduleParticipation.schedule.scheduleTimeTable.endDay, scheduleParticipation.participant.recruitment.title, + scheduleParticipation.id, storage.imagePath, + scheduleParticipation.schedule.scheduleTimeTable.endDay, scheduleParticipation.recruitmentParticipation.recruitment.title, scheduleParticipation.schedule.address.sido, scheduleParticipation.schedule.address.sigungu, - scheduleParticipation.participant.recruitment.volunteeringCategory, scheduleParticipation.participant.recruitment.volunteeringType, - scheduleParticipation.participant.recruitment.isIssued, scheduleParticipation.participant.recruitment.volunteerType, + scheduleParticipation.recruitmentParticipation.recruitment.volunteeringCategory, scheduleParticipation.recruitmentParticipation.recruitment.volunteeringType, + scheduleParticipation.recruitmentParticipation.recruitment.isIssued, scheduleParticipation.recruitmentParticipation.recruitment.volunteerType, scheduleParticipation.schedule.scheduleTimeTable.progressTime )) .from(scheduleParticipation) @@ -99,10 +99,10 @@ public Slice findHistoryDtos(Long loginUserNo, Pageable pageab .where( ltscheduleParticipationNo(lastId) , scheduleParticipation.state.eq(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL) - , scheduleParticipation.participant.user.userNo.eq(loginUserNo) + , scheduleParticipation.recruitmentParticipation.user.userNo.eq(loginUserNo) ) .limit(pageable.getPageSize() + 1) - .orderBy(scheduleParticipation.scheduleParticipationNo.desc()) + .orderBy(scheduleParticipation.id.desc()) .fetch(); return checkEndPage(pageable, results); } @@ -110,7 +110,7 @@ public Slice findHistoryDtos(Long loginUserNo, Pageable pageab // no-offset 방식 처리하는 메서드 private BooleanExpression ltscheduleParticipationNo(Long scheduleParticipationNo) { return (scheduleParticipationNo != null) - ?scheduleParticipation.scheduleParticipationNo.lt(scheduleParticipationNo) + ?scheduleParticipation.id.lt(scheduleParticipationNo) :null; } diff --git a/src/main/java/project/volunteer/global/common/component/Timetable.java b/src/main/java/project/volunteer/global/common/component/Timetable.java index 7848b0ca..ee5cbb27 100644 --- a/src/main/java/project/volunteer/global/common/component/Timetable.java +++ b/src/main/java/project/volunteer/global/common/component/Timetable.java @@ -57,4 +57,15 @@ public boolean isDoneByEndDate(LocalDate now) { return endDay.isBefore(now); } + @Override + public String toString() { + return "Timetable{" + + "startDay=" + startDay + + ", endDay=" + endDay + + ", hourFormat=" + hourFormat + + ", startTime=" + startTime + + ", progressTime=" + progressTime + + '}'; + } + } diff --git a/src/main/java/project/volunteer/global/error/exception/ErrorCode.java b/src/main/java/project/volunteer/global/error/exception/ErrorCode.java index 5c85d090..9350f6b6 100644 --- a/src/main/java/project/volunteer/global/error/exception/ErrorCode.java +++ b/src/main/java/project/volunteer/global/error/exception/ErrorCode.java @@ -50,11 +50,13 @@ public enum ErrorCode { INVALID_CONTENT_SIZE(HttpStatus.BAD_REQUEST, "invalid.content.size"), EXCEED_PARTICIPATION_NUM_THAN_RECRUITMENT_PARTICIPATION_NUM(HttpStatus.BAD_REQUEST, "exceed.participation.num.than.recruitment.participation.num"), - LESS_PARTICIPATION_NUM_THAN_CURRENT_PARTICIPANT(HttpStatus.BAD_REQUEST, - "less.participation.num.then.current.participant"), EXPIRED_PERIOD_SCHEDULE(HttpStatus.BAD_REQUEST, "expired.period.schedule"), NOT_EXIST_SCHEDULE(HttpStatus.BAD_REQUEST, "notExist.schedule"), + // 일정 참여 관련 + NOT_EXIST_SCHEDULE_PARTICIPATION(HttpStatus.BAD_REQUEST, "notExist.schedule.participation"), + DUPLICATE_SCHEDULE_PARTICIPATION(HttpStatus.BAD_REQUEST, "duplicate.schedule.participation"), + //로그보드 관련 NOT_EXIST_LOGBOARD(HttpStatus.BAD_REQUEST, "notExist.logboard"), diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 3b6f078c..030aee76 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -27,10 +27,14 @@ insufficient.capacity = \uBAA8\uC9D1\uC778\uC6D0\uC774 \uB9C8\uAC10\uB418\uC5C8\ invalid.organizationName.size = \uAE30\uAD00 \uC774\uB984\uC758 \uAE38\uC774\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. invalid.content.size = \uBCF8\uBB38 \uAE38\uC774\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. exceed.participation.num.than.recruitment.participation.num = \uC77C\uC815 \uCC38\uC5EC \uC778\uC6D0 \uC218\uB294 \uBAA8\uC9D1\uAE00 \uCC38\uC5EC \uC778\uC6D0 \uC218\uB97C \uCD08\uACFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. -less.participation.num.then.current.participant = \uC77C\uC815 \uCC38\uC5EC \uC778\uC6D0 \uC218\uB294 \uD604\uC7AC \uC77C\uC815 \uCC38\uC5EC\uC790 \uC218\uBCF4\uB2E4 \uC801\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. expired.period.schedule = \uC77C\uC815 \uBAA8\uC9D1 \uAE30\uAC04\uC774 \uC885\uB8CC \uB418\uC5C8\uC2B5\uB2C8\uB2E4. notExist.schedule = \uD574\uB2F9 \uC77C\uC815 \uC815\uBCF4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +#\uC77C\uCCA8 \uCC38\uC5EC \uAD00\uB828 +notExist.schedule.participation = \uC77C\uC815 \uCC38\uC5EC \uC815\uBCF4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +duplicate.schedule.participation = \uC77C\uCCAD \uC911\uBCF5 \uC2E0\uCCAD\uC744 \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC2E0\uCCAD \uD604\uD669\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694. + + #\uB85C\uADF8 \uAD00\uB828 notExist.logboard = \uD574\uB2F9 \uBD09\uC0AC\uB85C\uADF8 \uC815\uBCF4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. forbidden.logboard = \uD574\uB2F9 \uBD09\uC0AC\uB85C\uADF8\uC5D0 \uB300\uD55C \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java index 8bddf805..ff452207 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java @@ -215,7 +215,7 @@ public void approveCancellationSchedule() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(newSp.getScheduleParticipationNo()); + CancelApproval dto = new CancelApproval(newSp.getId()); //when ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", @@ -253,7 +253,7 @@ public void cancelApprove_forbidden() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(newSp.getScheduleParticipationNo()); + CancelApproval dto = new CancelApproval(newSp.getId()); //when & then mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) @@ -290,7 +290,7 @@ public void approvalCompletionSchedule() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - CompleteApproval dto = new CompleteApproval(List.of(newSp.getScheduleParticipationNo())); + CompleteApproval dto = new CompleteApproval(List.of(newSp.getId())); //when ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/complete", @@ -391,11 +391,11 @@ public void findListCancellationRequesterSchedule() throws Exception { //then result.andExpect(status().isOk()) - .andExpect(jsonPath("$.cancelling[0].scheduleParticipationNo").value(scheduleParticipation1.getScheduleParticipationNo())) + .andExpect(jsonPath("$.cancelling[0].scheduleParticipationNo").value(scheduleParticipation1.getId())) .andExpect(jsonPath("$.cancelling[0].nickname").value(test1.getNickName())) .andExpect(jsonPath("$.cancelling[0].email").value(test1.getEmail())) .andExpect(jsonPath("$.cancelling[0].profile").value(test1.getPicture())) - .andExpect(jsonPath("$.cancelling[1].scheduleParticipationNo").value(scheduleParticipation2.getScheduleParticipationNo())) + .andExpect(jsonPath("$.cancelling[1].scheduleParticipationNo").value(scheduleParticipation2.getId())) .andExpect(jsonPath("$.cancelling[1].nickname").value(test2.getNickName())) .andExpect(jsonPath("$.cancelling[1].email").value(test2.getEmail())) .andExpect(jsonPath("$.cancelling[1].profile").value(test2.getPicture())) diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipationTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipationTest.java new file mode 100644 index 00000000..2a854ea2 --- /dev/null +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipationTest.java @@ -0,0 +1,72 @@ +package project.volunteer.domain.scheduleParticipation.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; +import java.time.LocalTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import project.volunteer.domain.recruitment.domain.Recruitment; +import project.volunteer.domain.recruitment.domain.VolunteerType; +import project.volunteer.domain.recruitment.domain.VolunteeringCategory; +import project.volunteer.domain.recruitment.domain.VolunteeringType; +import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; +import project.volunteer.domain.sehedule.domain.Schedule; +import project.volunteer.domain.user.domain.Gender; +import project.volunteer.domain.user.domain.Role; +import project.volunteer.domain.user.domain.User; +import project.volunteer.global.common.component.Address; +import project.volunteer.global.common.component.Coordinate; +import project.volunteer.global.common.component.HourFormat; +import project.volunteer.global.common.component.IsDeleted; +import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.common.component.Timetable; + +class ScheduleParticipationTest { + private final Timetable timetable = new Timetable(LocalDate.now(), LocalDate.now(), HourFormat.AM, LocalTime.now(), + 10); + private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); + private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); + private final Recruitment recruitment = Recruitment.builder() + .title("title") + .content("content") + .volunteeringCategory(VolunteeringCategory.EDUCATION) + .volunteerType(VolunteerType.ADULT) + .volunteeringType(VolunteeringType.IRREG) + .maxParticipationNum(9999) + .currentVolunteerNum(0) + .isIssued(true) + .organizationName("organization") + .address(address) + .coordinate(coordinate) + .timetable(timetable) + .viewCount(0) + .likeCount(0) + .isPublished(true) + .isDeleted(IsDeleted.N) + .build(); + private final User user = new User("test", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null); + private final RecruitmentParticipation recruitmentParticipation = new RecruitmentParticipation(recruitment, user, + ParticipantState.JOIN_APPROVAL); + private final Schedule schedule = new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, + recruitment); + + + @ParameterizedTest + @EnumSource(names = {"PARTICIPATING", "PARTICIPATION_CANCEL"}) + @DisplayName("PARTICIPATION_CANCEL_APPROVAL 상태만 재 일정 참여가 가능하다.") + void checkReParticipation(ParticipantState invalidState) { + //given + ScheduleParticipation scheduleParticipation = new ScheduleParticipation(schedule, recruitmentParticipation, + invalidState); + + //when + Boolean result = scheduleParticipation.canReParticipation(); + + //then + assertThat(result).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java index 6c3c7f61..b0c45767 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java @@ -120,7 +120,7 @@ private Schedule createAndSaveSchedule(LocalDate fromDate, LocalDate toDate) { private Long createAndSaveScheduleParticipation(Schedule schedule, RecruitmentParticipation participant) { ScheduleParticipation scheduleParticipation = new ScheduleParticipation(schedule, participant, ParticipantState.PARTICIPATING); - return scheduleParticipationRepository.save(scheduleParticipation).getScheduleParticipationNo(); + return scheduleParticipationRepository.save(scheduleParticipation).getId(); } private ScheduleParticipation findBy(Long scheduleParticipationNo) { diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java new file mode 100644 index 00000000..c896306b --- /dev/null +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java @@ -0,0 +1,146 @@ +package project.volunteer.domain.scheduleParticipation.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.LocalDate; +import java.time.LocalTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import project.volunteer.domain.recruitment.domain.Recruitment; +import project.volunteer.domain.recruitment.domain.VolunteerType; +import project.volunteer.domain.recruitment.domain.VolunteeringCategory; +import project.volunteer.domain.recruitment.domain.VolunteeringType; +import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; +import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.sehedule.domain.Schedule; +import project.volunteer.domain.user.domain.Gender; +import project.volunteer.domain.user.domain.Role; +import project.volunteer.domain.user.domain.User; +import project.volunteer.global.common.component.Address; +import project.volunteer.global.common.component.Coordinate; +import project.volunteer.global.common.component.HourFormat; +import project.volunteer.global.common.component.IsDeleted; +import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.common.component.Timetable; +import project.volunteer.global.error.exception.BusinessException; +import project.volunteer.global.error.exception.ErrorCode; +import project.volunteer.support.ServiceTest; + +class ScheduleParticipationCommandUseCaseTest extends ServiceTest { + private final Timetable timetable = new Timetable(LocalDate.now(), LocalDate.now(), HourFormat.AM, LocalTime.now(), + 10); + private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); + private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); + private final User user = new User("test1", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null); + private final Recruitment recruitment = Recruitment.builder() + .title("title") + .content("content") + .volunteeringCategory(VolunteeringCategory.EDUCATION) + .volunteerType(VolunteerType.ADULT) + .volunteeringType(VolunteeringType.IRREG) + .maxParticipationNum(9999) + .currentVolunteerNum(0) + .isIssued(true) + .organizationName("organization") + .address(address) + .coordinate(coordinate) + .timetable(timetable) + .viewCount(0) + .likeCount(0) + .isPublished(true) + .isDeleted(IsDeleted.N) + .build(); + + @BeforeEach + void setUp() { + userRepository.save(user); + recruitmentRepository.save(recruitment); + } + + @DisplayName("일정 첫 참여에 성공하고, 일정 참여 인원을 늘린다.") + @Test + void participate() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); + + //when + Long id = scheduleParticipationCommandUseCase.participate(schedule, recruitmentParticipation); + + //then + ScheduleParticipation scheduleParticipation = findScheduleParticipation(id); + assertAll( + () -> assertThat(scheduleParticipation.getState()).isEqualByComparingTo(ParticipantState.PARTICIPATING), + () -> assertThat(schedule.getCurrentVolunteerNum()).isEqualTo(1) + ); + } + + @DisplayName("일정 참여 인원이 가득찬 경우 예외를 발생시킨다.") + @Test + void participateFullSchedule() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 10, recruitment)); + + //when & then + assertThatThrownBy(() -> scheduleParticipationCommandUseCase.participate(schedule, recruitmentParticipation)) + .isInstanceOf(BusinessException.class) + .hasMessage(ErrorCode.INSUFFICIENT_CAPACITY.name()); + } + + @DisplayName("중복 참여를 할 경우 예외를 발생시킨다.") + @Test + void duplicationParticipate() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); + + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation, ParticipantState.PARTICIPATING)); + + //when & then + assertThatThrownBy(() -> scheduleParticipationCommandUseCase.participate(schedule, recruitmentParticipation)) + .isInstanceOf(BusinessException.class) + .hasMessage(ErrorCode.DUPLICATE_SCHEDULE_PARTICIPATION.name()); + } + + @DisplayName("일정 재 참여에 성공하고, 일정 참여 인원을 늘린다.") + @Test + void reParticipate() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); + + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation, + ParticipantState.PARTICIPATION_CANCEL_APPROVAL)); + + //when + Long id = scheduleParticipationCommandUseCase.participate(schedule, recruitmentParticipation); + + //then + ScheduleParticipation scheduleParticipation = findScheduleParticipation(id); + assertAll( + () -> assertThat(scheduleParticipation.getState()).isEqualByComparingTo(ParticipantState.PARTICIPATING), + () -> assertThat(schedule.getCurrentVolunteerNum()).isEqualTo(1) + ); + } + + private ScheduleParticipation findScheduleParticipation(Long id) { + return scheduleParticipationRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("일정 참여 정보가 존재하지 않습니다.")); + } + +} \ No newline at end of file diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java index 2fbd5ee0..8e07c3f6 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java @@ -79,104 +79,6 @@ void init(){ saveSchedule = scheduleRepository.save(createSchedule); } - @Test - @DisplayName("일정 첫 참가에 성공하다.") - public void schedule_participating(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation participant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - - //when - spService.participate(saveSchedule, participant); - clear(); - - //then - ScheduleParticipation findSP = scheduleParticipationRepository.findByUserNoAndScheduleNo(newUser.getUserNo(), saveSchedule.getScheduleNo()).get(); - assertThat(findSP.getState()).isEqualTo(ParticipantState.PARTICIPATING); - assertThat(saveSchedule.getCurrentVolunteerNum()).isEqualTo(1); - } - - //TODO: 일정 서비스 테스트로 이동해야할 테스트 -// @Test -// @DisplayName("일정 기간 종료로 인해 참가 신청에 실패하다.") -// public void schedule_period_end(){ -// //given -// User newUser = 사용자_등록("kubonsik"); -// 봉사모집글_팀원_등록(saveRecruitment, newUser); -// -// Timetable changeTime = Timetable.createTimetable( -// LocalDate.now().minusDays(1), LocalDate.now().minusDays(1), HourFormat.AM,LocalTime.now(),3); -// saveSchedule.changeScheduleTime(changeTime); -// clear(); -// -// //when & then -// assertThatThrownBy(() -> spService.participate(saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo(), newUser.getUserNo())) -// .isInstanceOf(BusinessException.class) -// .hasMessageContaining(ErrorCode.EXPIRED_PERIOD_SCHEDULE.name()); -// } - - @Test - @DisplayName("일정 모집 인원 초가로 인해 참가 신청에 실패하다.") - public void schedule_volunteerNum_insufficient(){ - //given - User newUser1 = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipant(); - - User newUser2 = 사용자_등록("honggildong"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipant(); - - User newUser3 = 사용자_등록("kuhara"); - RecruitmentParticipation newParticipant3 = 봉사모집글_팀원_등록(saveRecruitment, newUser3); - 일정_참여자_상태_추가(saveSchedule, newParticipant3, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipant(); - - User newUser4 = 사용자_등록("parkhayoung"); - RecruitmentParticipation participant = 봉사모집글_팀원_등록(saveRecruitment, newUser4); - clear(); - - //when & then - assertThatThrownBy(() -> spService.participate(saveSchedule, participant)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining("INSUFFICIENT_CAPACITY"); - } - - @Test - @DisplayName("일정 참가 신청을 중복하다.") - public void schedule_participating_duplication(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipant(); - clear(); - - //when & then - assertThatThrownBy(() -> spService.participate(saveSchedule, newParticipant)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.DUPLICATE_RECRUITMENT_PARTICIPATION.name()); - } - - @Test - @DisplayName("일정 재신청에 성공하다.") - public void schedule_reParticipating(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL_APPROVAL); - clear(); - - //when - spService.participate(saveSchedule, newParticipant); - - //then - ScheduleParticipation findSP = scheduleParticipationRepository.findByUserNoAndScheduleNo(newUser.getUserNo(), saveSchedule.getScheduleNo()).get(); - assertThat(findSP.getState()).isEqualTo(ParticipantState.PARTICIPATING); - } - @Test @DisplayName("일정 참여 취소 요청에 성공하다.") public void schedule_cancelParticipation(){ @@ -184,7 +86,7 @@ public void schedule_cancelParticipation(){ User newUser = 사용자_등록("kubonsik"); RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipant(); + saveSchedule.increaseParticipationNum(1); clear(); //when @@ -217,15 +119,15 @@ public void schedule_cancelApprove(){ //given User newUser = 사용자_등록("kubonsik"); RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - saveSchedule.increaseParticipant(); + saveSchedule.increaseParticipationNum(1); ScheduleParticipation newSp = 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); //when - spService.approvalCancellation(saveSchedule, newSp.getScheduleParticipationNo()); + spService.approvalCancellation(saveSchedule, newSp.getId()); clear(); //then - ScheduleParticipation findSp = scheduleParticipationRepository.findById(newSp.getScheduleParticipationNo()).get(); + ScheduleParticipation findSp = scheduleParticipationRepository.findById(newSp.getId()).get(); assertThat(findSp.getState()).isEqualTo(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); assertThat(saveSchedule.getCurrentVolunteerNum()).isEqualTo(0); } @@ -243,15 +145,15 @@ public void schedule_completeApprove(){ RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); ScheduleParticipation newSp2 = 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - List spNos = List.of(newSp1.getScheduleParticipationNo(), newSp2.getScheduleParticipationNo()); + List spNos = List.of(newSp1.getId(), newSp2.getId()); clear(); //when spService.approvalCompletion(spNos); //then - ScheduleParticipation findSp1 = scheduleParticipationRepository.findById(newSp1.getScheduleParticipationNo()).get(); - ScheduleParticipation findSp2 = scheduleParticipationRepository.findById(newSp2.getScheduleParticipationNo()).get(); + ScheduleParticipation findSp1 = scheduleParticipationRepository.findById(newSp1.getId()).get(); + ScheduleParticipation findSp2 = scheduleParticipationRepository.findById(newSp2.getId()).get(); assertThat(findSp1.getState()).isEqualTo(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); assertThat(findSp2.getState()).isEqualTo(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); } @@ -269,7 +171,7 @@ public void schedule_completeApprove_invalid_state(){ RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); ScheduleParticipation newSp2 = 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); //유효하지 않은 상태 - List spNos = List.of(newSp1.getScheduleParticipationNo(), newSp2.getScheduleParticipationNo()); + List spNos = List.of(newSp1.getId(), newSp2.getId()); clear(); //when & then diff --git a/src/test/java/project/volunteer/domain/sehedule/domain/ScheduleTest.java b/src/test/java/project/volunteer/domain/sehedule/domain/ScheduleTest.java index 6efec1c4..c5ce2c08 100644 --- a/src/test/java/project/volunteer/domain/sehedule/domain/ScheduleTest.java +++ b/src/test/java/project/volunteer/domain/sehedule/domain/ScheduleTest.java @@ -222,7 +222,7 @@ void throwExceptionWhenChangeExceedParticipationNumThanCurrentParticipationNum(i assertThatThrownBy( () -> schedule.change(recruitment, timetable, "test", "test", address, invalidParticipationNum)) .isInstanceOf(BusinessException.class) - .hasMessage(ErrorCode.LESS_PARTICIPATION_NUM_THAN_CURRENT_PARTICIPANT.name()); + .hasMessage(ErrorCode.INVALID_CURRENT_PARTICIPATION_NUM.name()); } @ParameterizedTest diff --git a/src/test/java/project/volunteer/support/ServiceTest.java b/src/test/java/project/volunteer/support/ServiceTest.java index e0dd0357..cbf5869d 100644 --- a/src/test/java/project/volunteer/support/ServiceTest.java +++ b/src/test/java/project/volunteer/support/ServiceTest.java @@ -15,6 +15,7 @@ import project.volunteer.domain.recruitment.repository.RecruitmentRepository; import project.volunteer.domain.recruitment.repository.RepeatPeriodRepository; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; import project.volunteer.domain.sehedule.application.ScheduleCommandUseCase; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; import project.volunteer.domain.sehedule.repository.ScheduleRepository; @@ -58,6 +59,9 @@ public abstract class ServiceTest { @Autowired protected RecruitmentParticipationUseCase recruitmentParticipationUseCase; + @Autowired + protected ScheduleParticipationCommandUseCase scheduleParticipationCommandUseCase; + @SpyBean protected Clock clock; From 3eb99a8fed9daf3da7812de9b580067e0a8df57d Mon Sep 17 00:00:00 2001 From: BonSik Date: Tue, 20 Feb 2024 20:09:32 +0900 Subject: [PATCH 03/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20Facade=20Layer=20command/query=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentParticipationService.java | 9 ----- .../RecruitmentParticipationUseCase.java | 6 +-- .../api/ScheduleParticipantController.java | 20 +++++----- .../ScheduleParticipationCommandFacade.java} | 37 ++---------------- .../ScheduleParticipationQueryFacade.java | 38 +++++++++++++++++++ .../application/ScheduleQueryUseCase.java | 2 +- 6 files changed, 55 insertions(+), 57 deletions(-) rename src/main/java/project/volunteer/domain/scheduleParticipation/{mapper/ScheduleParticipantFacade.java => service/ScheduleParticipationCommandFacade.java} (55%) create mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java diff --git a/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationService.java b/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationService.java index 949bd04a..740bc4ec 100644 --- a/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationService.java +++ b/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationService.java @@ -104,15 +104,6 @@ public void deleteRecruitmentParticipations(Long recruitmentNo) { .forEach(RecruitmentParticipation::delete); } - - - - - - - - - @Override public RecruitmentParticipation findParticipation(Long recruitmentNo, Long userNo) { return recruitmentParticipationRepository.findByRecruitment_RecruitmentNoAndUser_UserNo(recruitmentNo, userNo) diff --git a/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationUseCase.java b/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationUseCase.java index 31985bbe..e4b44058 100644 --- a/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationUseCase.java +++ b/src/main/java/project/volunteer/domain/recruitmentParticipation/application/RecruitmentParticipationUseCase.java @@ -18,12 +18,8 @@ public interface RecruitmentParticipationUseCase { void deleteRecruitmentParticipations(Long recruitmentNo); - - + RecruitmentParticipation findParticipation(Long recruitmentNo, Long userNo); //팀 탈퇴(미정) - - RecruitmentParticipation findParticipation(Long recruitmentNo, Long userNo); - } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index e7990205..08da2ea4 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -4,7 +4,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import project.volunteer.domain.scheduleParticipation.api.dto.*; -import project.volunteer.domain.scheduleParticipation.mapper.ScheduleParticipantFacade; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandFacade; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryFacade; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; @@ -18,19 +19,20 @@ @RequiredArgsConstructor @RequestMapping("/recruitment") public class ScheduleParticipantController { - private final ScheduleParticipantFacade scheduleParticipantFacade; + private final ScheduleParticipationCommandFacade scheduleParticipationCommandFacade; + private final ScheduleParticipationQueryFacade scheduleParticipationQueryFacade; @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_TEAM) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/join") public ResponseEntity scheduleParticipation(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - scheduleParticipantFacade.participateVolunteerPostSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); + scheduleParticipationCommandFacade.participateSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); return ResponseEntity.ok().build(); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_TEAM) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancel") public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - scheduleParticipantFacade.cancelParticipationVolunteerPostSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); + scheduleParticipationCommandFacade.cancelParticipationVolunteerPostSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); return ResponseEntity.ok().build(); } @@ -38,7 +40,7 @@ public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @P @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancelling") public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, @RequestBody @Valid CancelApproval dto){ - scheduleParticipantFacade.approvalCancellationVolunteerPostSchedule(scheduleNo, dto.getNo()); + scheduleParticipationCommandFacade.approvalCancellationVolunteerPostSchedule(scheduleNo, dto.getNo()); return ResponseEntity.ok().build(); } @@ -47,7 +49,7 @@ public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, @RequestBody @Valid CompleteApproval dto){ - scheduleParticipantFacade.approvalCompletionVolunteerPostSchedule(scheduleNo, dto.getCompletedList()); + scheduleParticipationCommandFacade.approvalCompletionVolunteerPostSchedule(scheduleNo, dto.getCompletedList()); return ResponseEntity.ok().build(); } @@ -55,7 +57,7 @@ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/participating") public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - List participating = scheduleParticipantFacade.findParticipatingParticipantsSchedule(scheduleNo); + List participating = scheduleParticipationQueryFacade.findParticipatingParticipantsSchedule(scheduleNo); return ResponseEntity.ok(new ParticipatingParticipantListResponse(participating)); } @@ -63,7 +65,7 @@ public ResponseEntity scheduleParticipantL @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancelling") public ResponseEntity scheduleCancelledParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - List cancellingParticipants = scheduleParticipantFacade.findCancelledParticipantsSchedule(scheduleNo); + List cancellingParticipants = scheduleParticipationQueryFacade.findCancelledParticipantsSchedule(scheduleNo); return ResponseEntity.ok(new CancelledParticipantListResponse(cancellingParticipants)); } @@ -71,7 +73,7 @@ public ResponseEntity scheduleCancelledPartici @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/completion") public ResponseEntity scheduleCompletedParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - List completedParticipants = scheduleParticipantFacade.findCompletedParticipantsSchedule(scheduleNo); + List completedParticipants = scheduleParticipationQueryFacade.findCompletedParticipantsSchedule(scheduleNo); return ResponseEntity.ok(new CompletedParticipantListResponse(completedParticipants)); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java similarity index 55% rename from src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java index 82369c25..19791dfb 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/mapper/ScheduleParticipantFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java @@ -1,15 +1,10 @@ -package project.volunteer.domain.scheduleParticipation.mapper; +package project.volunteer.domain.scheduleParticipation.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.recruitmentParticipation.application.RecruitmentParticipationUseCase; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryUseCase; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; import project.volunteer.domain.sehedule.domain.Schedule; @@ -17,19 +12,15 @@ @Service @RequiredArgsConstructor -@Transactional(readOnly = true) -public class ScheduleParticipantFacade { +@Transactional +public class ScheduleParticipationCommandFacade { private final ScheduleQueryUseCase scheduleQueryUsecase; private final RecruitmentParticipationUseCase recruitmentParticipationUseCase; private final ScheduleParticipationCommandUseCase scheduleParticipationService; - private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; - @Transactional - public void participateVolunteerPostSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ + public void participateSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgressWithPERSSIMITIC_WRITE_LOCK(scheduleNo); - RecruitmentParticipation recruitmentParticipation = recruitmentParticipationUseCase.findParticipation(recruitmentNo, userNo); - scheduleParticipationService.participate(schedule, recruitmentParticipation); } @@ -40,7 +31,6 @@ public void participateVolunteerPostSchedule(Long userNo, Long recruitmentNo, Lo - @Transactional public void cancelParticipationVolunteerPostSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); @@ -49,35 +39,16 @@ public void cancelParticipationVolunteerPostSchedule(Long userNo, Long recruitme scheduleParticipationService.cancel(schedule, recruitmentParticipation); } - @Transactional public void approvalCancellationVolunteerPostSchedule(Long scheduleNo, Long scheduleParticipantNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNo); } - @Transactional public void approvalCompletionVolunteerPostSchedule(Long scheduleNo, List scheduleParticipantNos){ scheduleQueryUsecase.findActivitedSchedule(scheduleNo); scheduleParticipationService.approvalCompletion(scheduleParticipantNos); } - public List findParticipatingParticipantsSchedule(Long scheduleNo){ - Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - return scheduleParticipationDtoService.findParticipatingParticipants(schedule); - } - - public List findCancelledParticipantsSchedule(Long scheduleNo){ - Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - return scheduleParticipationDtoService.findCancelledParticipants(schedule); - } - - public List findCompletedParticipantsSchedule(Long scheduleNo){ - Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - return scheduleParticipationDtoService.findCompletedParticipants(schedule); - } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java new file mode 100644 index 00000000..b37fd0a9 --- /dev/null +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java @@ -0,0 +1,38 @@ +package project.volunteer.domain.scheduleParticipation.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; +import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; +import project.volunteer.domain.sehedule.domain.Schedule; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ScheduleParticipationQueryFacade { + private final ScheduleQueryUseCase scheduleQueryUsecase; + private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; + + public List findParticipatingParticipantsSchedule(Long scheduleNo){ + Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); + + return scheduleParticipationDtoService.findParticipatingParticipants(schedule); + } + + public List findCancelledParticipantsSchedule(Long scheduleNo){ + Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); + + return scheduleParticipationDtoService.findCancelledParticipants(schedule); + } + + public List findCompletedParticipantsSchedule(Long scheduleNo){ + Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); + + return scheduleParticipationDtoService.findCompletedParticipants(schedule); + } + +} diff --git a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleQueryUseCase.java b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleQueryUseCase.java index bc050e62..5256cb91 100644 --- a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleQueryUseCase.java +++ b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleQueryUseCase.java @@ -19,7 +19,7 @@ public interface ScheduleQueryUseCase { // 삭제 되지 않은 일정 Schedule findActivitedSchedule(Long scheduleNo); - // 삭제 되지 않은 일정(비관적 락 사용) + // 삭제 되지 않고, 모집 중인 일정(비관적 락 사용) Schedule findScheduleInProgressWithPERSSIMITIC_WRITE_LOCK(Long scheduleNo); } From 64b96679d2c1f88b694beaa183d8bfa5d64606c8 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 21 Feb 2024 17:57:00 +0900 Subject: [PATCH 04/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcurrentScheduleParticipationTest.java | 174 +++++++++++++++ .../service/ConcurrentTest.java | 206 ------------------ .../volunteer/support/DatabaseCleaner.java | 5 +- 3 files changed, 175 insertions(+), 210 deletions(-) create mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java delete mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentTest.java diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java new file mode 100644 index 00000000..c82d87cb --- /dev/null +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java @@ -0,0 +1,174 @@ +package project.volunteer.domain.scheduleParticipation.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ActiveProfiles; +import project.volunteer.domain.recruitment.domain.Recruitment; +import project.volunteer.domain.recruitment.domain.VolunteerType; +import project.volunteer.domain.recruitment.domain.VolunteeringCategory; +import project.volunteer.domain.recruitment.domain.VolunteeringType; +import project.volunteer.domain.recruitment.repository.RecruitmentRepository; +import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; +import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; +import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; +import project.volunteer.domain.sehedule.domain.Schedule; +import project.volunteer.domain.sehedule.repository.ScheduleRepository; +import project.volunteer.domain.user.dao.UserRepository; +import project.volunteer.domain.user.domain.Gender; +import project.volunteer.domain.user.domain.Role; +import project.volunteer.domain.user.domain.User; +import project.volunteer.global.common.component.Address; +import project.volunteer.global.common.component.Coordinate; +import project.volunteer.global.common.component.HourFormat; +import project.volunteer.global.common.component.IsDeleted; +import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.common.component.Timetable; +import project.volunteer.global.error.exception.BusinessException; +import project.volunteer.global.error.exception.ErrorCode; +import project.volunteer.support.DatabaseCleaner; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@SpringBootTest +@ActiveProfiles("test") +public class ConcurrentScheduleParticipationTest { + @Autowired + private UserRepository userRepository; + @Autowired + private RecruitmentRepository recruitmentRepository; + @Autowired + private ScheduleRepository scheduleRepository; + @Autowired + private RecruitmentParticipationRepository recruitmentParticipationRepository; + @Autowired + private ScheduleParticipationRepository scheduleParticipationRepository; + @Autowired + private ScheduleParticipationCommandFacade scheduleParticipationCommandFacade; + @SpyBean + private Clock clock; + @Autowired + private DatabaseCleaner databaseCleaner; + + private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); + private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); + + @AfterEach + void tearDown() { + databaseCleaner.execute(); + } + + @DisplayName("여러 회원이 동시에 일정 참여를 시도해도 참여 가능 인원 임계값을 넘지 못한다.") + @Test + @Order(1) + void participateWithConcurrent() throws InterruptedException { + // given + int numberOfThreads = 5; + CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); + ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); + List exceptions = Collections.synchronizedList(new ArrayList<>()); + + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + User writer = userRepository.save(new User("test", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null)); + + Recruitment recruitment = recruitmentRepository.save( + new Recruitment("title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.IRREG, + VolunteerType.ADULT, 999, 0, true, "organization", address, coordinate, + new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 2, 10), HourFormat.AM, + LocalTime.now(), 10), + 0, 0, true, IsDeleted.N, writer) + ); + + Schedule schedule = scheduleRepository.save( + new Schedule(new Timetable( + LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 10), HourFormat.AM, LocalTime.now(), 10), + "test", "unicef", address, 2, IsDeleted.N, 0, recruitment) + ); + + List users = createAndSaveUser(numberOfThreads); + users.forEach(user -> recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL))); + + // when + for (int i = 0; i < numberOfThreads; i++) { + User user = users.get(i); + + executorService.execute(() -> { + try { + scheduleParticipationCommandFacade.participateSchedule(user.getUserNo(), + recruitment.getRecruitmentNo(), schedule.getScheduleNo()); + } catch (BusinessException e) { + exceptions.add(e); + } finally { + countDownLatch.countDown(); + } + }); + } + countDownLatch.await(); + + //then + List findScheduleParticipation = scheduleParticipationRepository.findAll(); + Schedule findSchedule = scheduleRepository.findById(schedule.getScheduleNo()) + .orElseThrow(() -> new IllegalArgumentException("일정 정보가 존재하지 않습니다.")); + assertAll( + () -> assertThat(findScheduleParticipation).hasSize(2), + () -> assertThat(exceptions).hasSize(3) + .extracting("errorCode") + .containsExactlyInAnyOrder(ErrorCode.INSUFFICIENT_CAPACITY, ErrorCode.INSUFFICIENT_CAPACITY, + ErrorCode.INSUFFICIENT_CAPACITY), + () -> assertThat(findSchedule.getCurrentVolunteerNum()).isEqualTo(2) + ); + } + + @DisplayName("명시적으로 DB를 초기화 한다.") + @Test + @Order(2) + void validateRollback() { + long userCount = userRepository.count(); + long recruitmentCount = recruitmentRepository.count(); + long scheduleCount = scheduleRepository.count(); + long scheduleParticipationCount = scheduleParticipationRepository.count(); + + assertAll( + () -> assertThat(userCount).isEqualTo(0), + () -> assertThat(recruitmentCount).isEqualTo(0), + () -> assertThat(scheduleCount).isEqualTo(0), + () -> assertThat(scheduleParticipationCount).isEqualTo(0) + ); + } + + private List createAndSaveUser(int num) { + List users = new ArrayList<>(); + for (int i = 0; i < num; i++) { + User user = userRepository.save( + new User("test" + i, "test" + i, "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null) + ); + users.add(user); + } + return users; + } + +} diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentTest.java deleted file mode 100644 index 41b0caae..00000000 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentTest.java +++ /dev/null @@ -1,206 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.service; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.orm.ObjectOptimisticLockingFailureException; -import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; -import project.volunteer.domain.recruitment.domain.Recruitment; -import project.volunteer.domain.recruitment.domain.VolunteerType; -import project.volunteer.domain.recruitment.domain.VolunteeringCategory; -import project.volunteer.domain.recruitment.domain.VolunteeringType; -import project.volunteer.domain.recruitment.repository.RecruitmentRepository; -import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.sehedule.repository.ScheduleRepository; -import project.volunteer.domain.sehedule.domain.Schedule; -import project.volunteer.domain.user.dao.UserRepository; -import project.volunteer.domain.user.domain.Gender; -import project.volunteer.domain.user.domain.Role; -import project.volunteer.domain.user.domain.User; -import project.volunteer.global.common.component.*; -import project.volunteer.global.error.exception.BusinessException; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -@Slf4j -public class ConcurrentTest { - @Autowired UserRepository userRepository; - @Autowired RecruitmentRepository recruitmentRepository; - @Autowired ScheduleRepository scheduleRepository; - @Autowired - RecruitmentParticipationRepository participantRepository; - @Autowired - ParticipationServiceConcurrent participateService; - @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - - User writer; - Recruitment saveRecruitment; - Schedule saveSchedule; - @BeforeEach - void init(){ - //작성자 저장 - User writerUser = User.createUser("1234", "1234", "1234", "1234", Gender.M, LocalDate.now(), "1234", - true, true, true, Role.USER, "kakao", "1234", null); - writer = userRepository.save(writerUser); - - //모집글 저장 - Recruitment createRecruitment = new Recruitment( "title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.REG, - VolunteerType.ADULT, 9999,0,true, "unicef", - new Address("111", "11", "test", "test"), - new Coordinate(1.2F, 2.2F), - new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 3, 3), HourFormat.AM, - LocalTime.now(), 10), - 0, 0, true, IsDeleted.N, writerUser); - saveRecruitment = recruitmentRepository.save(createRecruitment); - - //일정 저장 - Schedule createSchedule = Schedule.create( - saveRecruitment, - new Timetable( - LocalDate.now().plusMonths(1), LocalDate.now().plusMonths(1), - HourFormat.AM, LocalTime.now(), 3), - "content", "organization", - Address.createAddress("11", "1111", "details", "fullName"), 1); - saveSchedule = scheduleRepository.save(createSchedule); - } - - @Test - @DisplayName("일정 참여 모집 인원은 1명인데, 동시성 이슈로 인해 3명의 사용자 모두 참여가 된다.") - @Disabled - public void concurrentParticipationWithoutLock() throws InterruptedException { - //given - final int numberOfThreads = 3; - - CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); - ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); - List participants = addTeamMember(numberOfThreads); - - //when - for(int i=0;i { - try{ - participateService.participateWithoutLock(saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo(), participant.getUser().getUserNo()); - }finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - - //then - List findSP = scheduleParticipationRepository.findBySchedule_ScheduleNo(saveSchedule.getScheduleNo()); - assertThat(findSP.size()).isEqualTo(3); - } - - @Test - @DisplayName("낙관적 락을 통해 동시성 문제를 해결해보자.") - @Disabled - public void concurrentParticipationWithOPTIMSTICLock() throws InterruptedException { - //given - final int numberOfThreads = 3; - - CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); - ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); - List participants = addTeamMember(numberOfThreads); - - //when - for(int i=0;i { - try{ - participateService.participateWithOPTIMSTICLock(saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo(), participant.getUser().getUserNo()); - }catch (ObjectOptimisticLockingFailureException e){ - log.info("동시성 문제 발견"); - /** - * 예외가 밸생해야됨. - * 예외 발생 시 "인원 마감" message 를 사용자에게 보여준다. -> 동시성 해결됬나?! - * "데드락" 발생(s-lock, x-lock)!!!(외래키가 있는 테이블에서는 낙관적 락으로 동시성을 해결 불가능!!) - * 만약 데드락이 발생하지 않더라도,여유자리가 많은 남은 상황에서 동시에 신청할 때는 예외가 발생하면 안됨. - * 또한 데드락이 발생하지 않고 마지막 인원의 신청자가 update 해주기 위해서 상태 Enum을 Schedule에 추가해 업데이트해주더라도, 남은 자리가 2자리고 동시 신청자가 3명일 경우 여전히 동시성 문제 해결 안됨. - * => 즉 동시성 이슈를 해결할 수 없음!! - */ - } - finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - - //then - Schedule findSchedule = scheduleRepository.findById(saveSchedule.getScheduleNo()).get(); - List findSP = scheduleParticipationRepository.findBySchedule_ScheduleNo(saveSchedule.getScheduleNo()); - assertThat(findSP.size()).isEqualTo(1); - assertThat(findSchedule.getCurrentVolunteerNum()).isEqualTo(1); - } - - @Test - @DisplayName("비관적 락을 통해 동시성 문제를 해결해보자.") - @Disabled - public void concurrentParticipationWithPERSSIMITIC_WRITE_Lock() throws InterruptedException { - //given - final int numberOfThreads = 3; - - CountDownLatch countDownLatch = new CountDownLatch(numberOfThreads); - ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); - List participants = addTeamMember(numberOfThreads); - - //when - for(int i=0;i { - try{ - participateService.participateWithPERSSIMITIC_WRITE_Lock(saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo(), participant.getUser().getUserNo()); - }catch (BusinessException e){ - log.info("인원 마감 : {}", e.getMessage()); - //인원 마감 "INSUFFICIENT_CAPACITY" 예외가 발생해야 함. - } - finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - - //then - Schedule findSchedule = scheduleRepository.findById(saveSchedule.getScheduleNo()).get(); - List findSP = scheduleParticipationRepository.findBySchedule_ScheduleNo(saveSchedule.getScheduleNo()); - assertThat(findSP.size()).isEqualTo(1); - assertThat(findSchedule.getCurrentVolunteerNum()).isEqualTo(1); - } - - - private List addTeamMember(int num){ - List participants = new ArrayList<>(); - for(int i=0;i tableNames; @PersistenceContext - EntityManager em; + private EntityManager em; @PostConstruct public void findAllTable() { @@ -27,9 +27,6 @@ public void findAllTable() { .map(javaType -> javaType.getAnnotation(Table.class)) .map(Table::name) .collect(Collectors.toList()); - - System.out.println("*********"); - System.out.println(tableNames.toString()); } @Transactional From 3e0abe2431508a7718f94637722da8e145d4f8a9 Mon Sep 17 00:00:00 2001 From: BonSik Date: Thu, 22 Feb 2024 01:56:21 +0900 Subject: [PATCH 05/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EB=8F=99=EC=8B=9C?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/component/Timetable.java | 13 ++++-- .../ConcurrentScheduleParticipationTest.java | 46 ++++++------------- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/main/java/project/volunteer/global/common/component/Timetable.java b/src/main/java/project/volunteer/global/common/component/Timetable.java index ee5cbb27..3aa0588e 100644 --- a/src/main/java/project/volunteer/global/common/component/Timetable.java +++ b/src/main/java/project/volunteer/global/common/component/Timetable.java @@ -1,7 +1,6 @@ package project.volunteer.global.common.component; import java.time.format.DateTimeFormatter; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,10 +12,8 @@ import java.time.LocalDate; import java.time.LocalTime; -@Builder @Getter @NoArgsConstructor -@AllArgsConstructor @Embeddable public class Timetable { private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MM-dd-yyyy"); @@ -38,6 +35,16 @@ public class Timetable { @Column(name = "progress_time", columnDefinition = "TINYINT", nullable = false) private Integer progressTime; //(1~24시간) + @Builder + public Timetable(LocalDate startDay, LocalDate endDay, HourFormat hourFormat, LocalTime startTime, + Integer progressTime) { + this.startDay = startDay; + this.endDay = endDay; + this.hourFormat = hourFormat; + this.startTime = startTime; + this.progressTime = progressTime; + } + public static Timetable of(String startDate, String endDate, String hourFormat, String startTime, int progressTime) { return Timetable.builder() diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java index c82d87cb..2508249e 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java @@ -16,10 +16,7 @@ import java.util.concurrent.Executors; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.SpyBean; @@ -49,7 +46,6 @@ import project.volunteer.global.error.exception.ErrorCode; import project.volunteer.support.DatabaseCleaner; -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @SpringBootTest @ActiveProfiles("test") public class ConcurrentScheduleParticipationTest { @@ -70,9 +66,6 @@ public class ConcurrentScheduleParticipationTest { @Autowired private DatabaseCleaner databaseCleaner; - private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); - private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); - @AfterEach void tearDown() { databaseCleaner.execute(); @@ -80,7 +73,6 @@ void tearDown() { @DisplayName("여러 회원이 동시에 일정 참여를 시도해도 참여 가능 인원 임계값을 넘지 못한다.") @Test - @Order(1) void participateWithConcurrent() throws InterruptedException { // given int numberOfThreads = 5; @@ -95,16 +87,21 @@ void participateWithConcurrent() throws InterruptedException { Recruitment recruitment = recruitmentRepository.save( new Recruitment("title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.IRREG, - VolunteerType.ADULT, 999, 0, true, "organization", address, coordinate, - new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 2, 10), HourFormat.AM, - LocalTime.now(), 10), + VolunteerType.ADULT, 999, 0, true, "organization", + Address.builder().sido("1111").sigungu("11").details("삼성 아파트").fullName("대구광역시 북구 삼성 아파트").build(), + Coordinate.builder().longitude(3.2F).latitude(3.2F).build(), + Timetable.builder().startDay(LocalDate.of(2024, 1, 10)).endDay(LocalDate.of(2024, 2, 10)) + .hourFormat(HourFormat.AM).startTime(LocalTime.now()).progressTime(10).build(), 0, 0, true, IsDeleted.N, writer) ); Schedule schedule = scheduleRepository.save( - new Schedule(new Timetable( - LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 10), HourFormat.AM, LocalTime.now(), 10), - "test", "unicef", address, 2, IsDeleted.N, 0, recruitment) + new Schedule( + Timetable.builder().startDay(LocalDate.of(2024, 2, 1)).endDay(LocalDate.of(2024, 2, 10)) + .hourFormat(HourFormat.AM).startTime(LocalTime.now()).progressTime(10).build(), + "test", "unicef", + Address.builder().sido("1111").sigungu("11").details("삼성 아파트").fullName("대구광역시 북구 삼성 아파트").build(), + 2, IsDeleted.N, 0, recruitment) ); List users = createAndSaveUser(numberOfThreads); @@ -142,26 +139,9 @@ void participateWithConcurrent() throws InterruptedException { ); } - @DisplayName("명시적으로 DB를 초기화 한다.") - @Test - @Order(2) - void validateRollback() { - long userCount = userRepository.count(); - long recruitmentCount = recruitmentRepository.count(); - long scheduleCount = scheduleRepository.count(); - long scheduleParticipationCount = scheduleParticipationRepository.count(); - - assertAll( - () -> assertThat(userCount).isEqualTo(0), - () -> assertThat(recruitmentCount).isEqualTo(0), - () -> assertThat(scheduleCount).isEqualTo(0), - () -> assertThat(scheduleParticipationCount).isEqualTo(0) - ); - } - - private List createAndSaveUser(int num) { + private List createAndSaveUser(int size) { List users = new ArrayList<>(); - for (int i = 0; i < num; i++) { + for (int i = 0; i < size; i++) { User user = userRepository.save( new User("test" + i, "test" + i, "test", "test@email.com", Gender.M, LocalDate.now(), "http://...", true, true, true, Role.USER, "kakao", "1234", null) From 5a002fdc737c7a382adf71c47b623f5aa1b7e0d6 Mon Sep 17 00:00:00 2001 From: BonSik Date: Sat, 24 Feb 2024 00:51:52 +0900 Subject: [PATCH 06/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?UseCase=20=EB=A1=9C=EC=A7=81=20=EB=82=B4=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 2 +- .../domain/ScheduleParticipation.java | 4 ++ .../ScheduleParticipationCommandFacade.java | 13 +++--- .../ScheduleParticipationCommandService.java | 28 ++++++------- .../ScheduleParticipationCommandUseCase.java | 4 +- ...heduleParticipationCommandUseCaseTest.java | 41 +++++++++++++++++++ .../ScheduleParticipationServiceImplTest.java | 34 --------------- 7 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index 08da2ea4..f6e9b3d3 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -32,7 +32,7 @@ public ResponseEntity scheduleParticipation(@PathVariable Long recruitmentNo, @P @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_TEAM) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancel") public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - scheduleParticipationCommandFacade.cancelParticipationVolunteerPostSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); + scheduleParticipationCommandFacade.cancelParticipationSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); return ResponseEntity.ok().build(); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java index 5cdb3869..c4a7a56c 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java @@ -44,6 +44,10 @@ public Boolean canReParticipation() { return state.equals(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); } + public Boolean canCancel() { + return state.equals(ParticipantState.PARTICIPATING); + } + public void changeState(ParticipantState state) { this.state = state; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java index 19791dfb..3badd9ab 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java @@ -24,6 +24,11 @@ public void participateSchedule(Long userNo, Long recruitmentNo, Long scheduleNo scheduleParticipationService.participate(schedule, recruitmentParticipation); } + public void cancelParticipationSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ + Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); + RecruitmentParticipation recruitmentParticipation = recruitmentParticipationUseCase.findParticipation(recruitmentNo, userNo); + scheduleParticipationService.cancelParticipation(schedule, recruitmentParticipation); + } @@ -31,14 +36,6 @@ public void participateSchedule(Long userNo, Long recruitmentNo, Long scheduleNo - public void cancelParticipationVolunteerPostSchedule(Long userNo, Long recruitmentNo, Long scheduleNo){ - Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); - - RecruitmentParticipation recruitmentParticipation = recruitmentParticipationUseCase.findParticipation(recruitmentNo, userNo); - - scheduleParticipationService.cancel(schedule, recruitmentParticipation); - } - public void approvalCancellationVolunteerPostSchedule(Long scheduleNo, Long scheduleParticipantNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index 3dddc2ad..169092d8 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -50,6 +50,19 @@ private void checkDuplicationParticipation(final ScheduleParticipation scheduleP } } + @Override + public void cancelParticipation(final Schedule schedule, final RecruitmentParticipation recruitmentParticipation) { + ScheduleParticipation scheduleParticipation = findScheduleParticipation(schedule, recruitmentParticipation); + checkCancellationPossible(scheduleParticipation); + scheduleParticipation.changeState(ParticipantState.PARTICIPATION_CANCEL); + } + + private void checkCancellationPossible(final ScheduleParticipation scheduleParticipation) { + if(!scheduleParticipation.canCancel()) { + throw new BusinessException(ErrorCode.INVALID_STATE, scheduleParticipation.toString()); + } + } + private ScheduleParticipation findScheduleParticipation(final Schedule schedule, final RecruitmentParticipation recruitmentParticipation) { return scheduleParticipationRepository.findByScheduleAndRecruitmentParticipation(schedule, @@ -71,21 +84,6 @@ private ScheduleParticipation findScheduleParticipation(final Schedule schedule, - - @Override - public void cancel(Schedule schedule, RecruitmentParticipation participant) { - //일정 참여 중 상태인지 검증 - ScheduleParticipation findSp = scheduleParticipationRepository.findByScheduleAndRecruitmentParticipationAndState( - schedule, - participant, ParticipantState.PARTICIPATING) - .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_STATE, - String.format("UserNo = [%d], ScheduleNo = [%d]", participant.getUser().getUserNo(), - schedule.getScheduleNo()))); - - //일정 신청 취소 요청 - findSp.changeState(ParticipantState.PARTICIPATION_CANCEL); - } - @Override public void approvalCancellation(Schedule schedule, Long spNo) { //일정 취소 요청 상태인지 검증 diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index a1e5a1bc..58ce51f7 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -9,11 +9,13 @@ public interface ScheduleParticipationCommandUseCase { Long participate(Schedule schedule, RecruitmentParticipation participant); + void cancelParticipation(Schedule schedule, RecruitmentParticipation participant); + + - public void cancel(Schedule schedule, RecruitmentParticipation participant); public void approvalCancellation(Schedule schedule, Long spNo); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java index c896306b..5a83e17b 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java @@ -34,6 +34,8 @@ class ScheduleParticipationCommandUseCaseTest extends ServiceTest { 10); private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); + private final User writer = new User("test", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null); private final User user = new User("test1", "test", "test", "test@email.com", Gender.M, LocalDate.now(), "http://...", true, true, true, Role.USER, "kakao", "1234", null); private final Recruitment recruitment = Recruitment.builder() @@ -53,10 +55,12 @@ class ScheduleParticipationCommandUseCaseTest extends ServiceTest { .likeCount(0) .isPublished(true) .isDeleted(IsDeleted.N) + .writer(writer) .build(); @BeforeEach void setUp() { + userRepository.save(writer); userRepository.save(user); recruitmentRepository.save(recruitment); } @@ -138,6 +142,43 @@ void reParticipate() { ); } + @DisplayName("일점 참여 취소에 성공한다.") + @Test + void cancelParticipation() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); + final ScheduleParticipation scheduleParticipation = scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation, ParticipantState.PARTICIPATING)); + + //when + scheduleParticipationCommandUseCase.cancelParticipation(schedule, recruitmentParticipation); + + //then + ScheduleParticipation findScheduleParticipation = findScheduleParticipation(scheduleParticipation.getId()); + assertThat(findScheduleParticipation.getState()).isEqualByComparingTo(ParticipantState.PARTICIPATION_CANCEL); + } + + @DisplayName("일정 참여 취소 전 상태가 PARTICIPATING가 아닐 경우, 예외를 발생시킨다.") + @Test + void cancelParticipationInvalidState() { + //given + final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation, ParticipantState.PARTICIPATION_CANCEL)); + + //when & then + assertThatThrownBy( + () -> scheduleParticipationCommandUseCase.cancelParticipation(schedule, recruitmentParticipation)) + .isInstanceOf(BusinessException.class) + .hasMessage(ErrorCode.INVALID_STATE.name()); + } + private ScheduleParticipation findScheduleParticipation(Long id) { return scheduleParticipationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("일정 참여 정보가 존재하지 않습니다.")); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java index 8e07c3f6..86baff3b 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java @@ -79,40 +79,6 @@ void init(){ saveSchedule = scheduleRepository.save(createSchedule); } - @Test - @DisplayName("일정 참여 취소 요청에 성공하다.") - public void schedule_cancelParticipation(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATING); - saveSchedule.increaseParticipationNum(1); - clear(); - - //when - spService.cancel(saveSchedule, newParticipant); - - //then - ScheduleParticipation findSp = scheduleParticipationRepository.findByUserNoAndScheduleNo(newUser.getUserNo(), saveSchedule.getScheduleNo()).get(); - assertThat(findSp.getState()).isEqualTo(ParticipantState.PARTICIPATION_CANCEL); - } - - @Test - @Transactional - @DisplayName("유효한 상태가 아니므로 일정 참여 취소 요청에 실패하다.") - public void schedule_cancelParticipation_invalid_state(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); //적절하지 않은 상태 - clear(); - - //when & then - assertThatThrownBy(() -> spService.cancel(saveSchedule, newParticipant)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining("INVALID_STATE"); - } - @Test @DisplayName("일정 참여 취소 요청 승인에 성공하다.") public void schedule_cancelApprove(){ From a3a3c97c937ffeb621fd552b0fba4e80f3b8431f Mon Sep 17 00:00:00 2001 From: BonSik Date: Sat, 24 Feb 2024 01:53:42 +0900 Subject: [PATCH 07/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20UseCase=20=EB=A1=9C=EC=A7=81=20=EB=82=B4?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 2 +- .../api/dto/CancelApproval.java | 3 +- .../domain/ScheduleParticipation.java | 4 + .../domain/ScheduleParticipations.java | 38 ++++ .../ScheduleParticipationRepository.java | 5 +- .../ScheduleParticipationCommandFacade.java | 8 +- .../ScheduleParticipationCommandService.java | 32 ++-- .../ScheduleParticipationCommandUseCase.java | 3 +- .../domain/sehedule/domain/Schedule.java | 5 +- .../acceptance/ScheduleAcceptanceTest.java | 4 +- .../ScheduleParticipantControllerTest.java | 4 +- ...heduleParticipationCommandUseCaseTest.java | 81 ++++++++- .../ScheduleParticipationServiceImplTest.java | 167 ------------------ 13 files changed, 149 insertions(+), 207 deletions(-) create mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java delete mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index f6e9b3d3..7c9b864b 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -40,7 +40,7 @@ public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @P @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancelling") public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, @RequestBody @Valid CancelApproval dto){ - scheduleParticipationCommandFacade.approvalCancellationVolunteerPostSchedule(scheduleNo, dto.getNo()); + scheduleParticipationCommandFacade.approvalCancellationSchedule(scheduleNo, dto.getNo()); return ResponseEntity.ok().build(); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java index d0e3445a..bc233d33 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java @@ -1,5 +1,6 @@ package project.volunteer.domain.scheduleParticipation.api.dto; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,5 +13,5 @@ public class CancelApproval { @NotNull - private Long no; + private List no; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java index c4a7a56c..8cdadf52 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java @@ -48,6 +48,10 @@ public Boolean canCancel() { return state.equals(ParticipantState.PARTICIPATING); } + public Boolean canApproveCancellation() { + return state.equals(ParticipantState.PARTICIPATION_CANCEL); + } + public void changeState(ParticipantState state) { this.state = state; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java new file mode 100644 index 00000000..b969a141 --- /dev/null +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java @@ -0,0 +1,38 @@ +package project.volunteer.domain.scheduleParticipation.domain; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.error.exception.BusinessException; +import project.volunteer.global.error.exception.ErrorCode; + +// 일급 컬렉션 +public class ScheduleParticipations { + private final List scheduleParticipants; + + public ScheduleParticipations(List scheduleParticipants) { + Objects.requireNonNull(scheduleParticipants); + scheduleParticipants.forEach(Objects::requireNonNull); + this.scheduleParticipants = Collections.unmodifiableList(scheduleParticipants); + } + + public int getSize() { + return scheduleParticipants.size(); + } + + public void approvalCancellations() { + validateApprovalCancellationPossible(); + scheduleParticipants.forEach( + scheduleParticipant -> scheduleParticipant.changeState(ParticipantState.PARTICIPATION_CANCEL_APPROVAL)); + } + + private void validateApprovalCancellationPossible() { + for (ScheduleParticipation scheduleParticipation : scheduleParticipants) { + if (!scheduleParticipation.canApproveCancellation()) { + throw new BusinessException(ErrorCode.INVALID_STATE, scheduleParticipation.toString()); + } + } + } + +} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java index e796e7ac..c11bc5a6 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java @@ -21,6 +21,9 @@ public interface ScheduleParticipationRepository extends JpaRepository findByScheduleAndRecruitmentParticipation(Schedule schedule, RecruitmentParticipation participant); + List findByIdIn(List ids); + + @@ -72,8 +75,6 @@ Optional findByScheduleAndRecruitmentParticipationAndStat Optional findByIdAndState(Long scheduleParticipationNo, ParticipantState state); - List findByIdIn(List spNos); - //N+1 문제를 막기 위해서 Projection + Join 방식 사용 @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java index 3badd9ab..0f0e2ad2 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java @@ -30,17 +30,17 @@ public void cancelParticipationSchedule(Long userNo, Long recruitmentNo, Long sc scheduleParticipationService.cancelParticipation(schedule, recruitmentParticipation); } + public void approvalCancellationSchedule(Long scheduleNo, List scheduleParticipantNo){ + Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); + scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNo); + } - public void approvalCancellationVolunteerPostSchedule(Long scheduleNo, Long scheduleParticipantNo){ - Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); - scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNo); - } public void approvalCompletionVolunteerPostSchedule(Long scheduleNo, List scheduleParticipantNos){ scheduleQueryUsecase.findActivitedSchedule(scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index 169092d8..1f0169b0 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; +import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipations; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; import project.volunteer.domain.sehedule.domain.Schedule; @@ -70,9 +71,22 @@ private ScheduleParticipation findScheduleParticipation(final Schedule schedule, .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE_PARTICIPATION)); } + @Override + public void approvalCancellation(Schedule schedule, List scheduleParticipationNo) { + ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNo); + scheduleParticipations.approvalCancellations(); + schedule.decreaseParticipationNum(scheduleParticipations.getSize()); + } + private ScheduleParticipations findScheduleParticipations(List ids) { + List scheduleParticipations = scheduleParticipationRepository.findByIdIn(ids); + if(ids.size() != scheduleParticipations.size()) { + throw new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE_PARTICIPATION); + } + return new ScheduleParticipations(scheduleParticipations); + } @@ -81,24 +95,6 @@ private ScheduleParticipation findScheduleParticipation(final Schedule schedule, - - - - @Override - public void approvalCancellation(Schedule schedule, Long spNo) { - //일정 취소 요청 상태인지 검증 - ScheduleParticipation findSp = scheduleParticipationRepository.findByIdAndState(spNo, - ParticipantState.PARTICIPATION_CANCEL) - .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_STATE, - String.format("ScheduleParticipationNo = [%d]", spNo))); - - //일정 취소 요청 승인 - findSp.changeState(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); - - //일정 참가자 수 감소 - schedule.decreaseParticipant(); - } - @Override public void approvalCompletion(List spNo) { scheduleParticipationRepository.findByIdIn(spNo).stream() diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index 58ce51f7..20591a1e 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -11,13 +11,14 @@ public interface ScheduleParticipationCommandUseCase { void cancelParticipation(Schedule schedule, RecruitmentParticipation participant); + void approvalCancellation(Schedule schedule, List scheduleParticipationNo); + - public void approvalCancellation(Schedule schedule, Long spNo); public void approvalCompletion(List spNo); diff --git a/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java b/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java index 05135bd4..e32abbfa 100644 --- a/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java +++ b/src/main/java/project/volunteer/domain/sehedule/domain/Schedule.java @@ -120,8 +120,9 @@ public void increaseParticipationNum(int addParticipationNum) { validateCurrentParticipationNum(this.volunteerNum, this.currentVolunteerNum); } - public void decreaseParticipant() { - this.currentVolunteerNum--; + public void decreaseParticipationNum(int subParticipationNum) { + this.currentVolunteerNum -= subParticipationNum; + validateCurrentParticipationNum(this.volunteerNum, this.currentVolunteerNum); } public Boolean isFull() { diff --git a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java index a21630bb..de99d537 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java @@ -760,7 +760,9 @@ void findScheduleAvailableStateAfterApprovalCancel() { 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, scheduleNo2); final CancelApproval cancelApprovalRequest = new CancelApproval( - cancelledParticipants.get(0).getScheduleParticipationNo()); + cancelledParticipants.stream() + .map(CancelledParticipantList::getScheduleParticipationNo) + .collect(Collectors.toList())); 봉사_일정_참여_취소승인(bonsikToken, recruitmentNo, scheduleNo2, cancelApprovalRequest); final List calendarSchedules = 캘린더_일정_조회(soeunToken, recruitmentNo, 2024, 2); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java index ff452207..613ace0c 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java @@ -215,7 +215,7 @@ public void approveCancellationSchedule() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(newSp.getId()); + CancelApproval dto = new CancelApproval(List.of(newSp.getId())); //when ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", @@ -253,7 +253,7 @@ public void cancelApprove_forbidden() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(newSp.getId()); + CancelApproval dto = new CancelApproval(List.of(newSp.getId())); //when & then mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java index 5a83e17b..d8cbe078 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java @@ -6,6 +6,7 @@ import java.time.LocalDate; import java.time.LocalTime; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,7 +37,9 @@ class ScheduleParticipationCommandUseCaseTest extends ServiceTest { private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); private final User writer = new User("test", "test", "test", "test@email.com", Gender.M, LocalDate.now(), "http://...", true, true, true, Role.USER, "kakao", "1234", null); - private final User user = new User("test1", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + private final User firstUser = new User("test1", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null); + private final User secondUser = new User("test2", "test", "test", "test@email.com", Gender.M, LocalDate.now(), "http://...", true, true, true, Role.USER, "kakao", "1234", null); private final Recruitment recruitment = Recruitment.builder() .title("title") @@ -61,7 +64,8 @@ class ScheduleParticipationCommandUseCaseTest extends ServiceTest { @BeforeEach void setUp() { userRepository.save(writer); - userRepository.save(user); + userRepository.save(firstUser); + userRepository.save(secondUser); recruitmentRepository.save(recruitment); } @@ -70,7 +74,7 @@ void setUp() { void participate() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); @@ -90,7 +94,7 @@ void participate() { void participateFullSchedule() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 10, recruitment)); @@ -105,7 +109,7 @@ void participateFullSchedule() { void duplicationParticipate() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); @@ -123,7 +127,7 @@ void duplicationParticipate() { void reParticipate() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); @@ -147,7 +151,7 @@ void reParticipate() { void cancelParticipation() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); final ScheduleParticipation scheduleParticipation = scheduleParticipationRepository.save( @@ -166,7 +170,7 @@ void cancelParticipation() { void cancelParticipationInvalidState() { //given final RecruitmentParticipation recruitmentParticipation = recruitmentParticipationRepository.save( - new RecruitmentParticipation(recruitment, user, ParticipantState.JOIN_APPROVAL)); + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); final Schedule schedule = scheduleRepository.save( new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 0, recruitment)); scheduleParticipationRepository.save( @@ -179,6 +183,67 @@ void cancelParticipationInvalidState() { .hasMessage(ErrorCode.INVALID_STATE.name()); } + @DisplayName("취소를 승인하고, 일정 참여 인원을 줄인다.") + @Test + void approveCancellation() { + //given + final RecruitmentParticipation recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); + final RecruitmentParticipation recruitmentParticipation2 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, secondUser, ParticipantState.JOIN_APPROVAL)); + + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + final List scheduleParticipationIds = List.of( + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation1, ParticipantState.PARTICIPATION_CANCEL)) + .getId(), + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation2, ParticipantState.PARTICIPATION_CANCEL)) + .getId()); + + //when + scheduleParticipationCommandUseCase.approvalCancellation(schedule, scheduleParticipationIds); + + //then + List scheduleParticipants = scheduleParticipationRepository.findAll(); + assertAll( + () -> assertThat(schedule.getCurrentVolunteerNum()).isEqualTo(1), + () -> assertThat(scheduleParticipants).hasSize(2) + .extracting("state") + .containsExactlyInAnyOrder(ParticipantState.PARTICIPATION_CANCEL_APPROVAL, + ParticipantState.PARTICIPATION_CANCEL_APPROVAL) + ); + } + + @DisplayName("취소 승인전 상태가 PARTICIPATION_CANCEL이 아닐 경우, 예외를 발생시킨다.") + @Test + void approveCancellationWithInvalidState() { + //given + final RecruitmentParticipation recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); + final RecruitmentParticipation recruitmentParticipation2 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, secondUser, ParticipantState.JOIN_APPROVAL)); + + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + final List scheduleParticipationIds = List.of( + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation1, ParticipantState.PARTICIPATING)) + .getId(), + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation2, ParticipantState.PARTICIPATING)) + .getId()); + + //when + assertThatThrownBy(() -> scheduleParticipationCommandUseCase.approvalCancellation(schedule, scheduleParticipationIds)) + .isInstanceOf(BusinessException.class) + .hasMessage(ErrorCode.INVALID_STATE.name()); + + } + private ScheduleParticipation findScheduleParticipation(Long id) { return scheduleParticipationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("일정 참여 정보가 존재하지 않습니다.")); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java deleted file mode 100644 index 86baff3b..00000000 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationServiceImplTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; -import project.volunteer.domain.recruitment.domain.Recruitment; -import project.volunteer.domain.recruitment.domain.VolunteerType; -import project.volunteer.domain.recruitment.domain.VolunteeringCategory; -import project.volunteer.domain.recruitment.domain.VolunteeringType; -import project.volunteer.domain.recruitment.repository.RecruitmentRepository; -import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.sehedule.repository.ScheduleRepository; -import project.volunteer.domain.sehedule.domain.Schedule; -import project.volunteer.domain.user.dao.UserRepository; -import project.volunteer.domain.user.domain.Gender; -import project.volunteer.domain.user.domain.Role; -import project.volunteer.domain.user.domain.User; -import project.volunteer.global.common.component.*; -import project.volunteer.global.error.exception.BusinessException; -import project.volunteer.global.error.exception.ErrorCode; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@SpringBootTest -@Transactional -class ScheduleParticipationServiceImplTest { - - @PersistenceContext EntityManager em; - @Autowired UserRepository userRepository; - @Autowired RecruitmentRepository recruitmentRepository; - @Autowired ScheduleRepository scheduleRepository; - @Autowired - RecruitmentParticipationRepository participantRepository; - @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired - ScheduleParticipationCommandUseCase spService; - - private User writer; - private Recruitment saveRecruitment; - private Schedule saveSchedule; - @BeforeEach - void init(){ - //작성자 저장 - User writerUser = User.createUser("1234", "1234", "1234", "1234", Gender.M, LocalDate.now(), "1234", - true, true, true, Role.USER, "kakao", "1234", null); - writer = userRepository.save(writerUser); - - //모집글 저장 - Recruitment createRecruitment = new Recruitment( "title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.REG, - VolunteerType.ADULT, 9999,0,true, "unicef", - new Address("111", "11", "test", "test"), - new Coordinate(1.2F, 2.2F), - new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 3, 3), HourFormat.AM, - LocalTime.now(), 10), - 0, 0, true, IsDeleted.N, writerUser); - saveRecruitment = recruitmentRepository.save(createRecruitment); - - //일정 저장 - Schedule createSchedule = Schedule.create( - saveRecruitment, - new Timetable( - LocalDate.now().plusMonths(1), LocalDate.now().plusMonths(1), - HourFormat.AM, LocalTime.now(), 3), - "content", "organization", - Address.createAddress("11", "1111", "details", "fullName"), 3); - saveSchedule = scheduleRepository.save(createSchedule); - } - - @Test - @DisplayName("일정 참여 취소 요청 승인에 성공하다.") - public void schedule_cancelApprove(){ - //given - User newUser = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, newUser); - saveSchedule.increaseParticipationNum(1); - ScheduleParticipation newSp = 일정_참여자_상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - - //when - spService.approvalCancellation(saveSchedule, newSp.getId()); - clear(); - - //then - ScheduleParticipation findSp = scheduleParticipationRepository.findById(newSp.getId()).get(); - assertThat(findSp.getState()).isEqualTo(ParticipantState.PARTICIPATION_CANCEL_APPROVAL); - assertThat(saveSchedule.getCurrentVolunteerNum()).isEqualTo(0); - } - - @Test - @Transactional - @DisplayName("일정 참여 완료 승인에 성공하다.") - public void schedule_completeApprove(){ - //given - User newUser1 = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - ScheduleParticipation newSp1 = 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - User newUser2 = 사용자_등록("yangsoeun"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - ScheduleParticipation newSp2 = 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - List spNos = List.of(newSp1.getId(), newSp2.getId()); - clear(); - - //when - spService.approvalCompletion(spNos); - - //then - ScheduleParticipation findSp1 = scheduleParticipationRepository.findById(newSp1.getId()).get(); - ScheduleParticipation findSp2 = scheduleParticipationRepository.findById(newSp2.getId()).get(); - assertThat(findSp1.getState()).isEqualTo(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); - assertThat(findSp2.getState()).isEqualTo(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); - } - - @Test - @Transactional - @DisplayName("유효한 상태가 아니므로 일정 참여 완료 승인에 실패하다.") - public void schedule_completeApprove_invalid_state(){ - //given - User newUser1 = 사용자_등록("kubonsik"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - ScheduleParticipation newSp1 = 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - User newUser2 = 사용자_등록("yangsoeun"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - ScheduleParticipation newSp2 = 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); //유효하지 않은 상태 - - List spNos = List.of(newSp1.getId(), newSp2.getId()); - clear(); - - //when & then - assertThatThrownBy(() -> spService.approvalCompletion(spNos)) - .isInstanceOf(BusinessException.class) - .hasMessageContaining(ErrorCode.INVALID_STATE.name()); - } - - private User 사용자_등록(String username){ - User createUser = User.createUser(username, username, username, username, Gender.M, LocalDate.now(), "picture", - true, true, true, Role.USER, "kakao", username, null); - return userRepository.save(createUser); - } - private RecruitmentParticipation 봉사모집글_팀원_등록(Recruitment recruitment, User user){ - RecruitmentParticipation participant = RecruitmentParticipation.createParticipant(recruitment, user, ParticipantState.JOIN_APPROVAL); - return participantRepository.save(participant); - } - private ScheduleParticipation 일정_참여자_상태_추가(Schedule schedule, RecruitmentParticipation participant, ParticipantState state){ - ScheduleParticipation scheduleParticipation = ScheduleParticipation.createScheduleParticipation(schedule, participant, state); - return scheduleParticipationRepository.save(scheduleParticipation); - } - private void clear() { - em.flush(); - em.clear(); - } - -} \ No newline at end of file From 1257e3219f27c8986f9df8c4a5d15eca59ee4a7a Mon Sep 17 00:00:00 2001 From: BonSik Date: Sat, 24 Feb 2024 01:55:02 +0900 Subject: [PATCH 08/29] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipationServiceConcurrent.java | 187 ------------------ 1 file changed, 187 deletions(-) delete mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java deleted file mode 100644 index 98a31f8e..00000000 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ParticipationServiceConcurrent.java +++ /dev/null @@ -1,187 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; -import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.sehedule.repository.ScheduleRepository; -import project.volunteer.domain.sehedule.domain.Schedule; -import project.volunteer.global.common.component.ParticipantState; -import project.volunteer.global.error.exception.BusinessException; -import project.volunteer.global.error.exception.ErrorCode; - -/** - * 동시성 테스트를 위한 Service - */ -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class ParticipationServiceConcurrent { - - private final ScheduleRepository scheduleRepository; - private final RecruitmentParticipationRepository participantRepository; - private final ScheduleParticipationRepository scheduleParticipationRepository; - - @Transactional - public void participateWithoutLock(Long recruitmentNo, Long scheduleNo, Long loginUserNo) { - //일정 검증(존재 여부, 모집 기간) - Schedule findSchedule = isActiveScheduleWithoutLock(scheduleNo); - - /** - * - 별다른 추가 필드 없이 쿼리를 통해 참가자 인원 수 체크하는 방식 - * - 동시성 이슈가 발생(두 번의 갱신 분실 문제) - */ - Integer activeParticipantNum = scheduleParticipationRepository.countActiveParticipant(scheduleNo); - if(findSchedule.getVolunteerNum() == activeParticipantNum){ - throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, - String.format("ScheduleNo = [%d], Active participant num = [%d]", findSchedule.getScheduleNo(), findSchedule.getCurrentVolunteerNum())); - } - - scheduleParticipationRepository.findByUserNoAndScheduleNo(loginUserNo, scheduleNo) - .ifPresentOrElse( - sp -> { - //중복 신청 검증(일정 참여중, 일정 참여 취소 요청) - if(sp.isEqualState(ParticipantState.PARTICIPATING) || sp.isEqualState(ParticipantState.PARTICIPATION_CANCEL)){ - throw new BusinessException(ErrorCode.DUPLICATE_RECRUITMENT_PARTICIPATION, - String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", findSchedule.getScheduleNo(), loginUserNo, sp.getState().name())); - } - - //재신청 - sp.changeState(ParticipantState.PARTICIPATING); - }, - () ->{ - //신규 신청 - RecruitmentParticipation findParticipant = - participantRepository.findByRecruitment_RecruitmentNoAndUser_UserNo(recruitmentNo, loginUserNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_RECRUITMENT_PARTICIPANT, - String.format("ScheduleNo = [%d], UserNo = [%d]", scheduleNo, loginUserNo))); - - ScheduleParticipation createSP = - ScheduleParticipation.createScheduleParticipation(findSchedule, findParticipant, ParticipantState.PARTICIPATING); - scheduleParticipationRepository.save(createSP); - } - ); - } - - @Transactional - public void participateWithOPTIMSTICLock(Long recruitmentNo, Long scheduleNo, Long loginUserNo) { - //일정 검증(존재 여부, 모집 기간) - Schedule findSchedule = isActiveScheduleWithOPTIMSTICLock(scheduleNo); - - if(findSchedule.isFull()) { - throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, - String.format("ScheduleNo = [%d], Active participant num = [%d]", findSchedule.getScheduleNo(), findSchedule.getCurrentVolunteerNum())); - } - - scheduleParticipationRepository.findByUserNoAndScheduleNo(loginUserNo, scheduleNo) - .ifPresentOrElse( - sp -> { - //중복 신청 검증(일정 참여중, 일정 참여 취소 요청) - if (sp.isEqualState(ParticipantState.PARTICIPATING) || sp.isEqualState(ParticipantState.PARTICIPATION_CANCEL)) { - throw new BusinessException(ErrorCode.DUPLICATE_RECRUITMENT_PARTICIPATION, - String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", findSchedule.getScheduleNo(), loginUserNo, sp.getState().name())); - } - //재신청 - sp.changeState(ParticipantState.PARTICIPATING); - }, - () -> { - //신규 신청 - RecruitmentParticipation findParticipant = - participantRepository.findByRecruitment_RecruitmentNoAndUser_UserNo(recruitmentNo, loginUserNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_RECRUITMENT_PARTICIPANT, - String.format("ScheduleNo = [%d], UserNo = [%d]", scheduleNo, loginUserNo))); - - ScheduleParticipation createSP = - ScheduleParticipation.createScheduleParticipation(findSchedule, findParticipant, ParticipantState.PARTICIPATING); - scheduleParticipationRepository.save(createSP); - }); - - findSchedule.increaseParticipationNum(1); - } - - @Transactional - public void participateWithPERSSIMITIC_WRITE_Lock(Long recruitmentNo, Long scheduleNo, Long loginUserNo) { - //일정 검증(존재 여부, 모집 기간) - Schedule findSchedule = isActiveScheduleWithPERSSIMITIC_WRITE_Lock(scheduleNo); - - if(findSchedule.isFull()){ - throw new BusinessException(ErrorCode.INSUFFICIENT_CAPACITY, - String.format("ScheduleNo = [%d], Active participant num = [%d]", findSchedule.getScheduleNo(), findSchedule.getCurrentVolunteerNum())); - } - - scheduleParticipationRepository.findByUserNoAndScheduleNo(loginUserNo, scheduleNo) - .ifPresentOrElse( - sp -> { - //중복 신청 검증(일정 참여중, 일정 참여 취소 요청) - if (sp.isEqualState(ParticipantState.PARTICIPATING) || sp.isEqualState(ParticipantState.PARTICIPATION_CANCEL)) { - throw new BusinessException(ErrorCode.DUPLICATE_RECRUITMENT_PARTICIPATION, - String.format("ScheduleNo = [%d], UserNo = [%d], State = [%s]", findSchedule.getScheduleNo(), loginUserNo, sp.getState().name())); - } - //재신청 - sp.changeState(ParticipantState.PARTICIPATING); - }, - () -> { - //신규 신청 - RecruitmentParticipation findParticipant = - participantRepository.findByRecruitment_RecruitmentNoAndUser_UserNo(recruitmentNo, loginUserNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_RECRUITMENT_PARTICIPANT, - String.format("ScheduleNo = [%d], UserNo = [%d]", scheduleNo, loginUserNo))); - - ScheduleParticipation createSP = - ScheduleParticipation.createScheduleParticipation(findSchedule, findParticipant, ParticipantState.PARTICIPATING); - scheduleParticipationRepository.save(createSP); - }); - - findSchedule.increaseParticipationNum(1); - } - - private Schedule isActiveScheduleWithoutLock(Long scheduleNo){ - //일정 조회(삭제되지 않은지만 검증) - Schedule findSchedule = scheduleRepository.findNotDeletedSchedule(scheduleNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE, - String.format("Schedule to participant = [%d]", scheduleNo))); - - //일정 마감 일자 조회 - if(!findSchedule.isAvailableDate()){ - throw new BusinessException(ErrorCode.EXPIRED_PERIOD_SCHEDULE, - String.format("ScheduleNo = [%d], participation period = [%s]", findSchedule.getScheduleNo(), findSchedule.getScheduleTimeTable().getEndDay().toString())); - } - - return findSchedule; - } - private Schedule isActiveScheduleWithOPTIMSTICLock(Long scheduleNo){ - //일정 조회(삭제되지 않은지만 검증) - Schedule findSchedule = scheduleRepository.findNotDeletedScheduleByOPTIMSTIC_LOCK(scheduleNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE, - String.format("Schedule to participant = [%d]", scheduleNo))); - - //일정 마감 일자 조회 - if(!findSchedule.isAvailableDate()){ - throw new BusinessException(ErrorCode.EXPIRED_PERIOD_SCHEDULE, - String.format("ScheduleNo = [%d], participation period = [%s]", findSchedule.getScheduleNo(), findSchedule.getScheduleTimeTable().getEndDay().toString())); - } - - return findSchedule; - } - private Schedule isActiveScheduleWithPERSSIMITIC_WRITE_Lock(Long scheduleNo){ - //일정 조회(삭제되지 않은지만 검증) - Schedule findSchedule = scheduleRepository.findNotDeletedScheduleByPERSSIMITIC_LOCK(scheduleNo) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE, - String.format("Schedule to participant = [%d]", scheduleNo))); - - //일정 마감 일자 조회 - if(!findSchedule.isAvailableDate()){ - throw new BusinessException(ErrorCode.EXPIRED_PERIOD_SCHEDULE, - String.format("ScheduleNo = [%d], participation period = [%s]", findSchedule.getScheduleNo(), findSchedule.getScheduleTimeTable().getEndDay().toString())); - } - - return findSchedule; - } - - -} From 081eec47003b02b571a93cdc07f1a45f42abf581 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 26 Feb 2024 19:51:25 +0900 Subject: [PATCH 09/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20UseCase=20=EB=A1=9C=EC=A7=81=20=EB=82=B4?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 2 +- .../domain/ScheduleParticipation.java | 4 ++ .../domain/ScheduleParticipations.java | 16 ++++- .../ScheduleParticipationCommandFacade.java | 12 +--- .../ScheduleParticipationCommandService.java | 27 +++----- .../ScheduleParticipationCommandUseCase.java | 5 +- ...heduleParticipationCommandUseCaseTest.java | 66 ++++++++++++++++++- 7 files changed, 97 insertions(+), 35 deletions(-) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index 7c9b864b..37863f88 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -49,7 +49,7 @@ public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, @RequestBody @Valid CompleteApproval dto){ - scheduleParticipationCommandFacade.approvalCompletionVolunteerPostSchedule(scheduleNo, dto.getCompletedList()); + scheduleParticipationCommandFacade.approvalParticipationCompletionSchedule(scheduleNo, dto.getCompletedList()); return ResponseEntity.ok().build(); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java index 8cdadf52..11cfa08a 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java @@ -52,6 +52,10 @@ public Boolean canApproveCancellation() { return state.equals(ParticipantState.PARTICIPATION_CANCEL); } + public Boolean canApproveParticipationCompletion() { + return state.equals(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); + } + public void changeState(ParticipantState state) { this.state = state; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java index b969a141..f13ecfd1 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipations.java @@ -21,7 +21,7 @@ public int getSize() { return scheduleParticipants.size(); } - public void approvalCancellations() { + public void approveCancellations() { validateApprovalCancellationPossible(); scheduleParticipants.forEach( scheduleParticipant -> scheduleParticipant.changeState(ParticipantState.PARTICIPATION_CANCEL_APPROVAL)); @@ -35,4 +35,18 @@ private void validateApprovalCancellationPossible() { } } + public void approveCompletionist() { + validateApprovalParticipationCompletionPossible(); + scheduleParticipants.forEach(scheduleParticipant -> scheduleParticipant.changeState( + ParticipantState.PARTICIPATION_COMPLETE_APPROVAL)); + } + + private void validateApprovalParticipationCompletionPossible() { + for (ScheduleParticipation scheduleParticipation : scheduleParticipants) { + if (!scheduleParticipation.canApproveParticipationCompletion()) { + throw new BusinessException(ErrorCode.INVALID_STATE, scheduleParticipation.toString()); + } + } + } + } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java index 0f0e2ad2..cff28538 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java @@ -32,20 +32,12 @@ public void cancelParticipationSchedule(Long userNo, Long recruitmentNo, Long sc public void approvalCancellationSchedule(Long scheduleNo, List scheduleParticipantNo){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); - scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNo); } - - - - - - - public void approvalCompletionVolunteerPostSchedule(Long scheduleNo, List scheduleParticipantNos){ + public void approvalParticipationCompletionSchedule(Long scheduleNo, List scheduleParticipantNos){ scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - scheduleParticipationService.approvalCompletion(scheduleParticipantNos); + scheduleParticipationService.approvalParticipationCompletion(scheduleParticipantNos); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index 1f0169b0..9855d9d8 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -72,13 +72,19 @@ private ScheduleParticipation findScheduleParticipation(final Schedule schedule, } @Override - public void approvalCancellation(Schedule schedule, List scheduleParticipationNo) { - ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNo); - scheduleParticipations.approvalCancellations(); + public void approvalCancellation(Schedule schedule, List scheduleParticipationNos) { + ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNos); + scheduleParticipations.approveCancellations(); schedule.decreaseParticipationNum(scheduleParticipations.getSize()); } + @Override + public void approvalParticipationCompletion(List scheduleParticipationNos) { + ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNos); + scheduleParticipations.approveCompletionist(); + } + private ScheduleParticipations findScheduleParticipations(List ids) { List scheduleParticipations = scheduleParticipationRepository.findByIdIn(ids); @@ -95,20 +101,7 @@ private ScheduleParticipations findScheduleParticipations(List ids) { - @Override - public void approvalCompletion(List spNo) { - scheduleParticipationRepository.findByIdIn(spNo).stream() - .forEach(sp -> { - //일정 참여 완료 미승인 상태가 아닌 경우 - if (!sp.isEqualState(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)) { - throw new BusinessException(ErrorCode.INVALID_STATE, - String.format("ScheduleParticipationNo = [%d], State = [%s]", - sp.getId(), sp.getState().name())); - } - //일정 참여 완료 승인 - sp.changeState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); - }); - } + @Override public void deleteScheduleParticipation(Long scheduleNo) { diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index 20591a1e..04b30498 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -11,8 +11,9 @@ public interface ScheduleParticipationCommandUseCase { void cancelParticipation(Schedule schedule, RecruitmentParticipation participant); - void approvalCancellation(Schedule schedule, List scheduleParticipationNo); + void approvalCancellation(Schedule schedule, List scheduleParticipationNos); + void approvalParticipationCompletion(List scheduleParticipationNos); @@ -20,8 +21,6 @@ public interface ScheduleParticipationCommandUseCase { - public void approvalCompletion(List spNo); - public void deleteScheduleParticipation(Long scheduleNo); public void deleteAllScheduleParticipation(Long recruitmentNo); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java index d8cbe078..f41477b6 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java @@ -197,10 +197,12 @@ void approveCancellation() { final List scheduleParticipationIds = List.of( scheduleParticipationRepository.save( - new ScheduleParticipation(schedule, recruitmentParticipation1, ParticipantState.PARTICIPATION_CANCEL)) + new ScheduleParticipation(schedule, recruitmentParticipation1, + ParticipantState.PARTICIPATION_CANCEL)) .getId(), scheduleParticipationRepository.save( - new ScheduleParticipation(schedule, recruitmentParticipation2, ParticipantState.PARTICIPATION_CANCEL)) + new ScheduleParticipation(schedule, recruitmentParticipation2, + ParticipantState.PARTICIPATION_CANCEL)) .getId()); //when @@ -238,12 +240,70 @@ void approveCancellationWithInvalidState() { .getId()); //when - assertThatThrownBy(() -> scheduleParticipationCommandUseCase.approvalCancellation(schedule, scheduleParticipationIds)) + assertThatThrownBy( + () -> scheduleParticipationCommandUseCase.approvalCancellation(schedule, scheduleParticipationIds)) .isInstanceOf(BusinessException.class) .hasMessage(ErrorCode.INVALID_STATE.name()); } + @DisplayName("일정 참여 완료 미승인된 사용자를 승인한다.") + @Test + void approveParticipationCompletion() { + //given + final RecruitmentParticipation recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); + final RecruitmentParticipation recruitmentParticipation2 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, secondUser, ParticipantState.JOIN_APPROVAL)); + + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + final List scheduleParticipationIds = List.of( + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation1, + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)) + .getId(), + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation2, + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)) + .getId()); + + //when + scheduleParticipationCommandUseCase.approvalParticipationCompletion(scheduleParticipationIds); + + //then + List scheduleParticipants = scheduleParticipationRepository.findAll(); + assertThat(scheduleParticipants).hasSize(2) + .extracting("state") + .containsExactlyInAnyOrder(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL, + ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); + } + + @DisplayName("일정 참여 완료 승인전 상태가 PARTICIPATION_COMPLETE_UNAPPROVED 이 아닐경우, 예외를 발생시킨다.") + @Test + void approveParticipationCompletionWithInvalidState() { + //given + final RecruitmentParticipation recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); + + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + final List scheduleParticipationIds = List.of( + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipation1, + ParticipantState.PARTICIPATING)) + .getId() + ); + + //when & then + assertThatThrownBy( + () -> scheduleParticipationCommandUseCase.approvalParticipationCompletion(scheduleParticipationIds)) + .isInstanceOf(BusinessException.class) + .hasMessage(ErrorCode.INVALID_STATE.name()); + } + private ScheduleParticipation findScheduleParticipation(Long id) { return scheduleParticipationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("일정 참여 정보가 존재하지 않습니다.")); From 911b1dd71b9b6510d163f2725926adefe4c0c8ed Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 26 Feb 2024 23:10:45 +0900 Subject: [PATCH 10/29] =?UTF-8?q?refactor:=20=EB=B4=89=EC=82=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20bulk=20update=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/RecruitmentFacade.java | 4 +- .../domain/ScheduleParticipation.java | 16 ++---- .../ScheduleParticipationRepository.java | 48 ++++++++--------- .../ScheduleParticipationCommandService.java | 33 ++++-------- .../ScheduleParticipationCommandUseCase.java | 9 +--- .../application/ScheduleCommandFacade.java | 2 +- .../repository/ScheduleRepository.java | 4 ++ .../ScheduleParticipationRepositoryTest.java | 53 +++++++++++++++---- ...heduleParticipationCommandUseCaseTest.java | 30 +++++++++++ 9 files changed, 119 insertions(+), 80 deletions(-) diff --git a/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java b/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java index 30b7363e..01d07e72 100644 --- a/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java +++ b/src/main/java/project/volunteer/domain/recruitment/application/RecruitmentFacade.java @@ -48,6 +48,7 @@ public void deleteRecruitment(Long recruitmentNo){ recruitmentParticipationUseCase.deleteRecruitmentParticipations(recruitmentNo); + scheduleParticipationService.deleteAllScheduleParticipationByRecruitment(recruitmentNo); @@ -60,9 +61,6 @@ public void deleteRecruitment(Long recruitmentNo){ //공지사항 확인 리스트 삭제 confirmationService.deleteAllConfirmation(RealWorkCode.NOTICE, noticeNoList); - - //일정 참여자 삭제 - scheduleParticipationService.deleteAllScheduleParticipation(recruitmentNo); } public StateResult findState(Long recruitmentNo, Long userNo){ diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java index 11cfa08a..ce06e841 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/domain/ScheduleParticipation.java @@ -13,13 +13,13 @@ @Getter @Entity -@Table(name = "vlt_schedule_participant") +@Table(name = "vlt_schedule_participation") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ScheduleParticipation extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "schedule_participant_no") + @Column(name = "schedule_participation_no") private Long id; @ManyToOne(fetch = FetchType.LAZY) @@ -79,6 +79,8 @@ public String toString() { + + public static ScheduleParticipation createScheduleParticipation(Schedule schedule, RecruitmentParticipation recruitmentParticipation, ParticipantState state){ ScheduleParticipation createScheduleParticipation = new ScheduleParticipation(); createScheduleParticipation.schedule = schedule; @@ -87,14 +89,4 @@ public static ScheduleParticipation createScheduleParticipation(Schedule schedul return createScheduleParticipation; } - public void delete(){ - this.state = ParticipantState.DELETED; - } - public void removeScheduleAndParticipant(){ - this.schedule = null; - this.recruitmentParticipation = null; - } - - public Boolean isEqualState(ParticipantState state) {return this.state.equals(state);} - } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java index c11bc5a6..239317bc 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java @@ -1,6 +1,7 @@ package project.volunteer.domain.scheduleParticipation.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,12 +18,33 @@ public interface ScheduleParticipationRepository extends JpaRepository, ScheduleParticipationQueryDSLRepository { - Boolean existsByScheduleAndRecruitmentParticipation(Schedule schedule, RecruitmentParticipation recruitmentParticipation); + Boolean existsByScheduleAndRecruitmentParticipation(Schedule schedule, + RecruitmentParticipation recruitmentParticipation); - Optional findByScheduleAndRecruitmentParticipation(Schedule schedule, RecruitmentParticipation participant); + Optional findByScheduleAndRecruitmentParticipation(Schedule schedule, + RecruitmentParticipation participant); List findByIdIn(List ids); + @Modifying(clearAutomatically = true) + @Query("UPDATE ScheduleParticipation sp " + + "SET sp.schedule = null, sp.recruitmentParticipation = null " + + "WHERE sp.schedule.scheduleNo = :scheduleNo") + void bulkUpdateDetachByScheduleNo(@Param("scheduleNo") Long scheduleNo); + + @Modifying(clearAutomatically = true) + @Query("UPDATE ScheduleParticipation sp " + + "SET sp.schedule = null, sp.recruitmentParticipation = null " + + "WHERE sp.schedule.scheduleNo in :scheduleNo") + void bulkUpdateDetachByScheduleNos(@Param("scheduleNo") List scheduleNos); + + + + + + + + @@ -44,22 +66,6 @@ Optional findByUserNoAndScheduleNo(@Param("userNo") Long "and sp.schedule.scheduleNo=:scheduleNo") Optional findStateBy(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo); - //일정 참여 중인 인원 수 반환 쿼리 - @Query("select count(sp) " + - "from ScheduleParticipation sp " + - "where sp.schedule.scheduleNo=:scheduleNo " + - "and sp.state=project.volunteer.global.common.component.ParticipantState.PARTICIPATING") - Integer countActiveParticipant(@Param("scheduleNo") Long scheduleNo); - - List findBySchedule_ScheduleNo(Long scheduleNo); - - @Query("select sp " + - "from ScheduleParticipation sp " + - "join sp.schedule s " + - "join s.recruitment r " + - "where r.recruitmentNo =:recruitmentNo") - List findByRecruitmentNo(@Param("recruitmentNo") Long recruitmentNo); - @Query("select sp " + "from ScheduleParticipation sp " + "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + @@ -69,12 +75,6 @@ Optional findByUserNoAndScheduleNoAndState(@Param("userNo @Param("scheduleNo") Long scheduleNo, @Param("state") ParticipantState state); - Optional findByScheduleAndRecruitmentParticipationAndState(Schedule schedule, RecruitmentParticipation participant, - ParticipantState state); - - Optional findByIdAndState(Long scheduleParticipationNo, - ParticipantState state); - //N+1 문제를 막기 위해서 Projection + Join 방식 사용 @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index 9855d9d8..7b42a731 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -8,6 +8,7 @@ import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; import project.volunteer.domain.sehedule.domain.Schedule; +import project.volunteer.domain.sehedule.repository.ScheduleRepository; import project.volunteer.global.common.component.ParticipantState; import project.volunteer.global.error.exception.BusinessException; import project.volunteer.global.error.exception.ErrorCode; @@ -19,6 +20,7 @@ @RequiredArgsConstructor public class ScheduleParticipationCommandService implements ScheduleParticipationCommandUseCase { private final ScheduleParticipationRepository scheduleParticipationRepository; + private final ScheduleRepository scheduleRepository; @Override public Long participate(final Schedule schedule, final RecruitmentParticipation recruitmentParticipation) { @@ -59,7 +61,7 @@ public void cancelParticipation(final Schedule schedule, final RecruitmentPartic } private void checkCancellationPossible(final ScheduleParticipation scheduleParticipation) { - if(!scheduleParticipation.canCancel()) { + if (!scheduleParticipation.canCancel()) { throw new BusinessException(ErrorCode.INVALID_STATE, scheduleParticipation.toString()); } } @@ -88,36 +90,21 @@ public void approvalParticipationCompletion(List scheduleParticipationNos) private ScheduleParticipations findScheduleParticipations(List ids) { List scheduleParticipations = scheduleParticipationRepository.findByIdIn(ids); - if(ids.size() != scheduleParticipations.size()) { + if (ids.size() != scheduleParticipations.size()) { throw new BusinessException(ErrorCode.NOT_EXIST_SCHEDULE_PARTICIPATION); } return new ScheduleParticipations(scheduleParticipations); } - - - - - - - - - @Override - public void deleteScheduleParticipation(Long scheduleNo) { - scheduleParticipationRepository.findBySchedule_ScheduleNo(scheduleNo) - .forEach(sp -> { - sp.delete(); - sp.removeScheduleAndParticipant(); - }); + public void deleteAllScheduleParticipationBySchedule(Long scheduleNo) { + scheduleParticipationRepository.bulkUpdateDetachByScheduleNo(scheduleNo); } @Override - public void deleteAllScheduleParticipation(Long recruitmentNo) { - scheduleParticipationRepository.findByRecruitmentNo(recruitmentNo) - .forEach(sp -> { - sp.delete(); - sp.removeScheduleAndParticipant(); - }); + public void deleteAllScheduleParticipationByRecruitment(Long recruitmentNo) { + List scheduleNos = scheduleRepository.findScheduleNosByRecruitmentNo(recruitmentNo); + scheduleParticipationRepository.bulkUpdateDetachByScheduleNos(scheduleNos); } + } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java index 04b30498..4e6b20a6 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCase.java @@ -15,13 +15,8 @@ public interface ScheduleParticipationCommandUseCase { void approvalParticipationCompletion(List scheduleParticipationNos); + void deleteAllScheduleParticipationBySchedule(Long scheduleNo); + void deleteAllScheduleParticipationByRecruitment(Long recruitmentNo); - - - - - public void deleteScheduleParticipation(Long scheduleNo); - - public void deleteAllScheduleParticipation(Long recruitmentNo); } \ No newline at end of file diff --git a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java index 8e3018f4..67a0b04a 100644 --- a/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java +++ b/src/main/java/project/volunteer/domain/sehedule/application/ScheduleCommandFacade.java @@ -31,7 +31,7 @@ public Long updateSchedule(Long recruitmentNo, Long scheduleNo, ScheduleUpsertCo public void deleteSchedule(Long scheduleNo) { scheduleCommandService.deleteSchedule(scheduleNo); - scheduleParticipationService.deleteScheduleParticipation(scheduleNo); + scheduleParticipationService.deleteAllScheduleParticipationBySchedule(scheduleNo); } } diff --git a/src/main/java/project/volunteer/domain/sehedule/repository/ScheduleRepository.java b/src/main/java/project/volunteer/domain/sehedule/repository/ScheduleRepository.java index c105dd6d..ab16ed04 100644 --- a/src/main/java/project/volunteer/domain/sehedule/repository/ScheduleRepository.java +++ b/src/main/java/project/volunteer/domain/sehedule/repository/ScheduleRepository.java @@ -40,4 +40,8 @@ public interface ScheduleRepository extends JpaRepository, Sched @Lock(LockModeType.OPTIMISTIC) Optional findNotDeletedScheduleByOPTIMSTIC_LOCK(@Param("no") Long scheduleNo); + @Query("SELECT s.scheduleNo " + + "FROM Schedule s " + + "WHERE s.recruitment.recruitmentNo = :recruitmentNo") + List findScheduleNosByRecruitmentNo(@Param("recruitmentNo") Long recruitmentNo); } diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java index b0c45767..f520bb12 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepositoryTest.java @@ -5,6 +5,7 @@ import com.amazonaws.services.kms.model.NotFoundException; import java.time.LocalDate; import java.time.LocalTime; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -53,8 +54,10 @@ class ScheduleParticipationRepositoryTest extends RepositoryTest { true, true, true, Role.USER, "test", "test", null); private final User user2 = new User("test2", "test2", "test2", "test2@test.com", Gender.M, LocalDate.now(), "test", true, true, true, Role.USER, "test", "test", null); - private final RecruitmentParticipation participant1 = new RecruitmentParticipation(recruitment, user1, ParticipantState.JOIN_APPROVAL); - private final RecruitmentParticipation participant2 = new RecruitmentParticipation(recruitment, user2, ParticipantState.JOIN_APPROVAL); + private final RecruitmentParticipation recruitmentParticipant1 = new RecruitmentParticipation(recruitment, user1, + ParticipantState.JOIN_APPROVAL); + private final RecruitmentParticipation recruitmentParticipant2 = new RecruitmentParticipation(recruitment, user2, + ParticipantState.JOIN_APPROVAL); @BeforeEach void setUp() { @@ -63,8 +66,8 @@ void setUp() { userRepository.save(user1); userRepository.save(user2); - participantRepository.save(participant1); - participantRepository.save(participant2); + participantRepository.save(recruitmentParticipant1); + participantRepository.save(recruitmentParticipant2); } @DisplayName("모집 종료된 일정 참여자들의 상태를 참여 중에서 미승인 참여 완료 상태로 변경한다.") @@ -74,15 +77,19 @@ void updateParticipantStateFinishedSchedule() { final LocalDate currentDate = LocalDate.of(2023, 1, 6); final Schedule finishedSchedule = createAndSaveSchedule(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 5)); - final Long scheduleParticipationNo1 = createAndSaveScheduleParticipation(finishedSchedule, participant1); - final Long scheduleParticipationNo2 = createAndSaveScheduleParticipation(finishedSchedule, participant2); + final Long scheduleParticipationNo1 = createAndSaveScheduleParticipation(finishedSchedule, + recruitmentParticipant1); + final Long scheduleParticipationNo2 = createAndSaveScheduleParticipation(finishedSchedule, + recruitmentParticipant2); //when scheduleParticipationRepository.unApprovedCompleteOfAllFinishedScheduleParticipant(currentDate); //then - assertThat(findBy(scheduleParticipationNo1).getState()).isEqualByComparingTo(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - assertThat(findBy(scheduleParticipationNo2).getState()).isEqualByComparingTo(ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); + assertThat(findBy(scheduleParticipationNo1).getState()).isEqualByComparingTo( + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); + assertThat(findBy(scheduleParticipationNo2).getState()).isEqualByComparingTo( + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); } @DisplayName("모집 중인 일정에 참가자들의 상태는 업데이트 없이 참여중 상태이다.") @@ -92,8 +99,10 @@ void notUpdateParticipantStateRecruitingSchedule() { final LocalDate currentDate = LocalDate.of(2023, 1, 6); final Schedule recruitingSchedule = createAndSaveSchedule(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 7)); - final Long scheduleParticipationNo1 = createAndSaveScheduleParticipation(recruitingSchedule, participant1); - final Long scheduleParticipationNo2 = createAndSaveScheduleParticipation(recruitingSchedule, participant2); + final Long scheduleParticipationNo1 = createAndSaveScheduleParticipation(recruitingSchedule, + recruitmentParticipant1); + final Long scheduleParticipationNo2 = createAndSaveScheduleParticipation(recruitingSchedule, + recruitmentParticipant2); //when scheduleParticipationRepository.unApprovedCompleteOfAllFinishedScheduleParticipant(currentDate); @@ -103,6 +112,30 @@ void notUpdateParticipantStateRecruitingSchedule() { assertThat(findBy(scheduleParticipationNo2).getState()).isEqualByComparingTo(ParticipantState.PARTICIPATING); } + @DisplayName("scheduleNo에 속한 모든 일정 참여 정보의 연관관계를 끊는다.") + @Test + void bulkUpdateDetachByScheduleNo() { + //given + final Schedule schedule = scheduleRepository.save( + new Schedule(timetable, "test", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipant1, ParticipantState.PARTICIPATING)); + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule, recruitmentParticipant2, ParticipantState.PARTICIPATION_CANCEL)); + + //when + scheduleParticipationRepository.bulkUpdateDetachByScheduleNo(schedule.getScheduleNo()); + + //then + List scheduleParticipations = scheduleParticipationRepository.findAll(); + assertThat(scheduleParticipations).hasSize(2) + .allSatisfy(scheduleParticipation -> { + assertThat(scheduleParticipation.getSchedule()).isNull(); + assertThat(scheduleParticipation.getRecruitmentParticipation()).isNull(); + }); + } + private Schedule createAndSaveSchedule(LocalDate fromDate, LocalDate toDate) { Schedule schedule = Schedule.builder() .timetable(new Timetable(fromDate, toDate, HourFormat.AM, LocalTime.now(), 10)) diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java index f41477b6..e80ec422 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandUseCaseTest.java @@ -304,6 +304,36 @@ void approveParticipationCompletionWithInvalidState() { .hasMessage(ErrorCode.INVALID_STATE.name()); } + @DisplayName("특정 봉사 모집글에 속한 모든 일정의 참여 정보를 삭제한다.") + @Test + void deleteAllScheduleParticipation() { + final RecruitmentParticipation recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, firstUser, ParticipantState.JOIN_APPROVAL)); + final RecruitmentParticipation recruitmentParticipation2 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment, secondUser, ParticipantState.JOIN_APPROVAL)); + + final Schedule schedule1 = scheduleRepository.save( + new Schedule(timetable, "test1", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + final Schedule schedule2 = scheduleRepository.save( + new Schedule(timetable, "test2", "unicef", address, 10, IsDeleted.N, 3, recruitment)); + + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule1, recruitmentParticipation1, ParticipantState.PARTICIPATING)); + scheduleParticipationRepository.save( + new ScheduleParticipation(schedule2, recruitmentParticipation2, ParticipantState.PARTICIPATION_CANCEL)); + + //when + scheduleParticipationCommandUseCase.deleteAllScheduleParticipationByRecruitment(recruitment.getRecruitmentNo()); + + //then + List scheduleParticipations = scheduleParticipationRepository.findAll(); + assertThat(scheduleParticipations).hasSize(2) + .allSatisfy(scheduleParticipation -> { + assertThat(scheduleParticipation.getSchedule()).isNull(); + assertThat(scheduleParticipation.getRecruitmentParticipation()).isNull(); + }); + } + private ScheduleParticipation findScheduleParticipation(Long id) { return scheduleParticipationRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("일정 참여 정보가 존재하지 않습니다.")); From 48a14e4629873c0b3a0de712568503280173415e Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 26 Feb 2024 23:21:04 +0900 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20enum=20=EC=83=81=EC=88=98=EA=B0=92=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../volunteer/global/common/component/ParticipantState.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/project/volunteer/global/common/component/ParticipantState.java b/src/main/java/project/volunteer/global/common/component/ParticipantState.java index cefd7f97..43065119 100644 --- a/src/main/java/project/volunteer/global/common/component/ParticipantState.java +++ b/src/main/java/project/volunteer/global/common/component/ParticipantState.java @@ -17,8 +17,7 @@ public enum ParticipantState implements CodeCommonType { PARTICIPATION_COMPLETE_UNAPPROVED("s4", "일정 참여 완료 미승인"), PARTICIPATION_COMPLETE_APPROVAL("s5", "일정 참가 완료 승인"), - DELETED("e1", "삭제") - ,; + ; private String code; private String desc; From b0144fc66a7bcf07c0ba2a6b3defcc8b5903213a Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 26 Feb 2024 23:35:47 +0900 Subject: [PATCH 12/29] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20final=20=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ScheduleParticipationCommandService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java index 7b42a731..c8aec13e 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandService.java @@ -74,7 +74,7 @@ private ScheduleParticipation findScheduleParticipation(final Schedule schedule, } @Override - public void approvalCancellation(Schedule schedule, List scheduleParticipationNos) { + public void approvalCancellation(final Schedule schedule, final List scheduleParticipationNos) { ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNos); scheduleParticipations.approveCancellations(); @@ -82,12 +82,12 @@ public void approvalCancellation(Schedule schedule, List scheduleParticipa } @Override - public void approvalParticipationCompletion(List scheduleParticipationNos) { + public void approvalParticipationCompletion(final List scheduleParticipationNos) { ScheduleParticipations scheduleParticipations = findScheduleParticipations(scheduleParticipationNos); scheduleParticipations.approveCompletionist(); } - private ScheduleParticipations findScheduleParticipations(List ids) { + private ScheduleParticipations findScheduleParticipations(final List ids) { List scheduleParticipations = scheduleParticipationRepository.findByIdIn(ids); if (ids.size() != scheduleParticipations.size()) { @@ -97,12 +97,12 @@ private ScheduleParticipations findScheduleParticipations(List ids) { } @Override - public void deleteAllScheduleParticipationBySchedule(Long scheduleNo) { + public void deleteAllScheduleParticipationBySchedule(final Long scheduleNo) { scheduleParticipationRepository.bulkUpdateDetachByScheduleNo(scheduleNo); } @Override - public void deleteAllScheduleParticipationByRecruitment(Long recruitmentNo) { + public void deleteAllScheduleParticipationByRecruitment(final Long recruitmentNo) { List scheduleNos = scheduleRepository.findScheduleNosByRecruitmentNo(recruitmentNo); scheduleParticipationRepository.bulkUpdateDetachByScheduleNos(scheduleNos); } From 52b8425024fb9f9afd5d30b6f691913ec8f185ef Mon Sep 17 00:00:00 2001 From: BonSik Date: Tue, 27 Feb 2024 00:14:32 +0900 Subject: [PATCH 13/29] =?UTF-8?q?refactor:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=A4=91=20=EC=9D=B8=EC=9B=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20UseCase=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 2 +- ...heduleParticipationQueryDSLRepository.java | 6 + ...leParticipationQueryDSLRepositoryImpl.java | 27 +++++ .../ScheduleParticipationRepository.java | 25 ++--- ....java => ScheduleParticipationDetail.java} | 12 +- .../ScheduleParticipationQueryFacade.java | 11 +- .../ScheduleParticipationQueryService.java | 24 ++-- .../ScheduleParticipationQueryUseCase.java | 8 +- .../dto/ParticipatingParticipantList.java | 11 +- ...heduleParticipationDtoServiceImplTest.java | 27 ----- ...ScheduleParticipationQueryUseCaseTest.java | 106 ++++++++++++++++++ .../volunteer/support/ServiceTest.java | 4 + 12 files changed, 192 insertions(+), 71 deletions(-) rename src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/{ParticipantDetails.java => ScheduleParticipationDetail.java} (65%) create mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index 37863f88..be4dca4f 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -57,7 +57,7 @@ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/participating") public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - List participating = scheduleParticipationQueryFacade.findParticipatingParticipantsSchedule(scheduleNo); + List participating = scheduleParticipationQueryFacade.findScheduleParticipatingList(scheduleNo); return ResponseEntity.ok(new ParticipatingParticipantListResponse(participating)); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepository.java index 5b1b196f..edb469fa 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepository.java @@ -1,7 +1,13 @@ package project.volunteer.domain.scheduleParticipation.repository; import java.time.LocalDate; +import java.util.List; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; +import project.volunteer.global.common.component.ParticipantState; public interface ScheduleParticipationQueryDSLRepository { void unApprovedCompleteOfAllFinishedScheduleParticipant(final LocalDate currentDate); + + List findScheduleParticipationDetailBy(Long scheduleNo, List states); + } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepositoryImpl.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepositoryImpl.java index b3835ebd..c500cf2c 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepositoryImpl.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationQueryDSLRepositoryImpl.java @@ -1,16 +1,24 @@ package project.volunteer.domain.scheduleParticipation.repository; +import static project.volunteer.domain.image.domain.QImage.image; +import static project.volunteer.domain.image.domain.QStorage.storage; +import static project.volunteer.domain.recruitmentParticipation.domain.QRecruitmentParticipation.recruitmentParticipation; import static project.volunteer.domain.scheduleParticipation.domain.QScheduleParticipation.scheduleParticipation; import static project.volunteer.domain.sehedule.domain.QSchedule.schedule; +import static project.volunteer.domain.user.domain.QUser.user; +import com.querydsl.core.types.Projections; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.LocalDate; +import java.util.List; import javax.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; import project.volunteer.global.common.component.IsDeleted; import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.common.component.RealWorkCode; @Repository @RequiredArgsConstructor @@ -34,6 +42,25 @@ public void unApprovedCompleteOfAllFinishedScheduleParticipant(final LocalDate c clear(); } + @Override + public List findScheduleParticipationDetailBy(final Long scheduleNo, + final List states) { + return factory.select( + Projections.constructor(ScheduleParticipationDetail.class, scheduleParticipation.id, user.nickName, + user.email, storage.imagePath.coalesce(user.picture), scheduleParticipation.state)) + .from(scheduleParticipation) + .join(scheduleParticipation.recruitmentParticipation, recruitmentParticipation) + .join(recruitmentParticipation.user, user) + .leftJoin(image) + .on(image.imageNo.eq(user.userNo), + image.realWorkCode.eq(RealWorkCode.USER), + image.isDeleted.eq(IsDeleted.N)) + .leftJoin(image.storage, storage) + .where(scheduleParticipation.schedule.scheduleNo.eq(scheduleNo), + scheduleParticipation.state.in(states)) + .fetch(); + } + private void clear() { em.flush(); em.clear(); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java index 239317bc..fbd97d26 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java @@ -7,7 +7,7 @@ import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; import project.volunteer.domain.scheduleParticipation.repository.dto.CompletedScheduleDetail; -import project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; @@ -38,6 +38,11 @@ Optional findByScheduleAndRecruitmentParticipation(Schedu + "WHERE sp.schedule.scheduleNo in :scheduleNo") void bulkUpdateDetachByScheduleNos(@Param("scheduleNo") List scheduleNos); + @Query("select sp.state " + + "from ScheduleParticipation sp " + + "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + + "and sp.schedule.scheduleNo=:scheduleNo") + Optional findStateBy(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo); @@ -53,18 +58,6 @@ Optional findByScheduleAndRecruitmentParticipation(Schedu - @Query("select sp " + - "from ScheduleParticipation sp " + - "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + - "and sp.schedule.scheduleNo=:scheduleNo") - Optional findByUserNoAndScheduleNo(@Param("userNo") Long userNo, - @Param("scheduleNo") Long scheduleNo); - - @Query("select sp.state " + - "from ScheduleParticipation sp " + - "join sp.recruitmentParticipation p on p.user.userNo=:userNo " + - "and sp.schedule.scheduleNo=:scheduleNo") - Optional findStateBy(@Param("userNo") Long userNo, @Param("scheduleNo") Long scheduleNo); @Query("select sp " + "from ScheduleParticipation sp " + @@ -76,7 +69,7 @@ Optional findByUserNoAndScheduleNoAndState(@Param("userNo @Param("state") ParticipantState state); //N+1 문제를 막기 위해서 Projection + Join 방식 사용 - @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ParticipantDetails(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " + "from ScheduleParticipation sp " + "join sp.recruitmentParticipation p " + @@ -88,8 +81,8 @@ Optional findByUserNoAndScheduleNoAndState(@Param("userNo "left join i.storage s " + "where sp.schedule.scheduleNo=:scheduleNo " + "and sp.state in :states ") - List findOptimizationParticipantByScheduleAndState(@Param("scheduleNo") Long scheduleNo, - @Param("states") List states); + List findOptimizationParticipantByScheduleAndState(@Param("scheduleNo") Long scheduleNo, + @Param("states") List states); @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.CompletedScheduleDetail" + "(sp.schedule.scheduleNo " + diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ParticipantDetails.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ScheduleParticipationDetail.java similarity index 65% rename from src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ParticipantDetails.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ScheduleParticipationDetail.java index de2663db..9cb743b7 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ParticipantDetails.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/dto/ScheduleParticipationDetail.java @@ -8,12 +8,12 @@ @AllArgsConstructor @NoArgsConstructor @Getter -public class ParticipantDetails { - Long scheduleParticipationNo; - String nickname; - String email; - String profile; - ParticipantState state; +public class ScheduleParticipationDetail { + private Long scheduleParticipationNo; + private String nickname; + private String email; + private String profile; + private ParticipantState state; public Boolean isEqualParticipantState(ParticipantState state){ return this.state.equals(state); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java index b37fd0a9..ef2ad244 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java @@ -17,12 +17,17 @@ public class ScheduleParticipationQueryFacade { private final ScheduleQueryUseCase scheduleQueryUsecase; private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; - public List findParticipatingParticipantsSchedule(Long scheduleNo){ + public List findScheduleParticipatingList(final Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - return scheduleParticipationDtoService.findParticipatingParticipants(schedule); + return scheduleParticipationDtoService.searchParticipatingList(schedule.getScheduleNo()); } + + + + + + public List findCancelledParticipantsSchedule(Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java index 39a2a7a4..de699d16 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java @@ -14,7 +14,6 @@ import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @Service @@ -24,17 +23,22 @@ public class ScheduleParticipationQueryService implements ScheduleParticipationQ private final ScheduleParticipationRepository scheduleParticipationRepository; @Override - public Optional searchState(Long scheduleNo, Long userNo) { - return scheduleParticipationRepository.findStateBy(userNo, scheduleNo); - } - - @Override - public List findParticipatingParticipants(Schedule schedule) { - return scheduleParticipationRepository.findOptimizationParticipantByScheduleAndState(schedule.getScheduleNo(), List.of(ParticipantState.PARTICIPATING)).stream() - .map(p -> new ParticipatingParticipantList(p.getNickname(), p.getEmail(), p.getProfile())) + public List searchParticipatingList(final Long scheduleNo) { + final List states = List.of(ParticipantState.PARTICIPATING); + return scheduleParticipationRepository.findScheduleParticipationDetailBy(scheduleNo, states) + .stream() + .map(ParticipatingParticipantList::from) .collect(Collectors.toList()); } + + + + + + + + @Override public List findCancelledParticipants(Schedule schedule) { return scheduleParticipationRepository.findOptimizationParticipantByScheduleAndState(schedule.getScheduleNo(), List.of(ParticipantState.PARTICIPATION_CANCEL)).stream() @@ -52,7 +56,7 @@ public List findCompletedParticipants(Schedule schedul }) .collect(Collectors.toList()); } - + @Override public List findCompleteScheduleList(Long loginUserNo, ParticipantState state) { return scheduleParticipationRepository.findCompletedSchedules(loginUserNo, state).stream() diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java index 36243829..62d16e18 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java @@ -1,6 +1,5 @@ package project.volunteer.domain.scheduleParticipation.service; -import java.util.Optional; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; @@ -11,12 +10,11 @@ import java.util.List; public interface ScheduleParticipationQueryUseCase { + List searchParticipatingList(Long scheduleNo); + + - //봉사 일정 참여 상태 조회 - Optional searchState(Long scheduleNo, Long userNo); - //참여 중 참가자 리스트 조회 - public List findParticipatingParticipants(Schedule schedule); //취소 요청 참가자 리스트 조회 public List findCancelledParticipants(Schedule schedule); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java index fe1de0d3..a002a2f9 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java @@ -3,13 +3,18 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; @AllArgsConstructor @NoArgsConstructor @Getter public class ParticipatingParticipantList { + private String nickname; + private String email; + private String profile; + + public static ParticipatingParticipantList from(ScheduleParticipationDetail detail) { + return new ParticipatingParticipantList(detail.getNickname(), detail.getEmail(), detail.getProfile()); + } - String nickname; - String email; - String profile; } diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java index 33207638..1543c64b 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java @@ -84,33 +84,6 @@ void init(){ saveSchedule = scheduleRepository.save(createSchedule); } - @Test - @DisplayName("일정 참여 중 리스트 조회 시 3명의 참여자가 조회된다.") - public void schedule_participant_list(){ - //given - User newUser1 = 사용자_등록("rnqhstlr"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATING); - - User newUser2 = 사용자_등록("didthdms"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATING); - - User newUser3 = 사용자_등록("didghfhr"); - RecruitmentParticipation newParticipant3 = 봉사모집글_팀원_등록(saveRecruitment, newUser3); - 일정_참여자_상태_추가(saveSchedule, newParticipant3, ParticipantState.PARTICIPATING); - clear(); - - //when - List findParticipants = spDtoService.findParticipatingParticipants(saveSchedule); - - //then - assertThat(findParticipants.size()).isEqualTo(3); - for(ParticipatingParticipantList p : findParticipants){ - assertThat(p.getProfile()).isEqualTo("picture"); - } - } - @Test @DisplayName("일정 취소 요청 리스트 조회 시 2명의 취소 요청자가 조회된다.") public void schedule_cancel_participant_list(){ diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java new file mode 100644 index 00000000..de3dcd21 --- /dev/null +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java @@ -0,0 +1,106 @@ +package project.volunteer.domain.scheduleParticipation.service; + +import static org.assertj.core.groups.Tuple.tuple; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import project.volunteer.domain.recruitment.domain.Recruitment; +import project.volunteer.domain.recruitment.domain.VolunteerType; +import project.volunteer.domain.recruitment.domain.VolunteeringCategory; +import project.volunteer.domain.recruitment.domain.VolunteeringType; +import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; +import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; +import project.volunteer.domain.sehedule.domain.Schedule; +import project.volunteer.domain.user.domain.Gender; +import project.volunteer.domain.user.domain.Role; +import project.volunteer.domain.user.domain.User; +import project.volunteer.global.common.component.Address; +import project.volunteer.global.common.component.Coordinate; +import project.volunteer.global.common.component.HourFormat; +import project.volunteer.global.common.component.IsDeleted; +import project.volunteer.global.common.component.ParticipantState; +import project.volunteer.global.common.component.Timetable; +import project.volunteer.support.ServiceTest; + +class ScheduleParticipationQueryUseCaseTest extends ServiceTest { + private final Timetable timetable = new Timetable(LocalDate.now(), LocalDate.now(), HourFormat.AM, LocalTime.now(), + 10); + private final Address address = new Address("1111", "111", "삼성 아파트", "대구광역시 북구 삼성 아파트"); + private final Coordinate coordinate = new Coordinate(1.2F, 2.2F); + private final User writer = new User("test", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://...", true, true, true, Role.USER, "kakao", "1234", null); + private final User firstUser = new User("test1", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://first...", true, true, true, Role.USER, "kakao", "1234", null); + private final User secondUser = new User("test2", "test", "test", "test@email.com", Gender.M, LocalDate.now(), + "http://second...", true, true, true, Role.USER, "kakao", "1234", null); + private final Recruitment recruitment = Recruitment.builder() + .title("title") + .content("content") + .volunteeringCategory(VolunteeringCategory.EDUCATION) + .volunteerType(VolunteerType.ADULT) + .volunteeringType(VolunteeringType.IRREG) + .maxParticipationNum(9999) + .currentVolunteerNum(0) + .isIssued(true) + .organizationName("organization") + .address(address) + .coordinate(coordinate) + .timetable(timetable) + .viewCount(0) + .likeCount(0) + .isPublished(true) + .isDeleted(IsDeleted.N) + .writer(writer) + .build(); + private final RecruitmentParticipation firstRecruitmentParticipation = new RecruitmentParticipation(recruitment, + firstUser, ParticipantState.JOIN_APPROVAL); + private final RecruitmentParticipation secondRecruitmentParticipation = new RecruitmentParticipation(recruitment, + secondUser, ParticipantState.JOIN_APPROVAL); + private final Schedule schedule = new Schedule(timetable, "test", "unicef", address, 100, IsDeleted.N, 0, + recruitment); + + @BeforeEach + void setUp() { + userRepository.save(writer); + userRepository.save(firstUser); + userRepository.save(secondUser); + + recruitmentRepository.save(recruitment); + + recruitmentParticipationRepository.save(firstRecruitmentParticipation); + recruitmentParticipationRepository.save(secondRecruitmentParticipation); + + scheduleRepository.save(schedule); + } + + @DisplayName("일정 참여자 리스트를 조회한다.") + @Test + void searchParticipantList() { + //given + final ScheduleParticipation scheduleParticipation1 = new ScheduleParticipation(schedule, + firstRecruitmentParticipation, ParticipantState.PARTICIPATING); + final ScheduleParticipation scheduleParticipation2 = new ScheduleParticipation(schedule, + secondRecruitmentParticipation, ParticipantState.PARTICIPATING); + + scheduleParticipationRepository.saveAll(List.of(scheduleParticipation1, scheduleParticipation2)); + + //when + List participatingParticipantLists = scheduleParticipationQueryUseCase.searchParticipatingList( + schedule.getScheduleNo()); + + //then + Assertions.assertThat(participatingParticipantLists).hasSize(2) + .extracting("nickname", "email", "profile") + .containsExactlyInAnyOrder( + tuple(firstUser.getNickName(), firstUser.getEmail(), firstUser.getPicture()), + tuple(secondUser.getNickName(), secondUser.getEmail(), secondUser.getPicture()) + ); + } + +} \ No newline at end of file diff --git a/src/test/java/project/volunteer/support/ServiceTest.java b/src/test/java/project/volunteer/support/ServiceTest.java index cbf5869d..560828de 100644 --- a/src/test/java/project/volunteer/support/ServiceTest.java +++ b/src/test/java/project/volunteer/support/ServiceTest.java @@ -16,6 +16,7 @@ import project.volunteer.domain.recruitment.repository.RepeatPeriodRepository; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; +import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryUseCase; import project.volunteer.domain.sehedule.application.ScheduleCommandUseCase; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; import project.volunteer.domain.sehedule.repository.ScheduleRepository; @@ -62,6 +63,9 @@ public abstract class ServiceTest { @Autowired protected ScheduleParticipationCommandUseCase scheduleParticipationCommandUseCase; + @Autowired + protected ScheduleParticipationQueryUseCase scheduleParticipationQueryUseCase; + @SpyBean protected Clock clock; From 8d62475aa95116c7956b5f779231dd078b02964a Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 17:54:06 +0900 Subject: [PATCH 14/29] =?UTF-8?q?refactor:=EC=9D=BC=EC=A0=95=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=B7=A8=EC=86=8C=20=EC=9D=B8=EC=9B=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20UseCase=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 10 ++--- .../dto/CancelledParticipantListResponse.java | 15 ------- .../ScheduleParticipationQueryFacade.java | 10 ++--- .../ScheduleParticipationQueryService.java | 43 +++++++++++-------- .../ScheduleParticipationQueryUseCase.java | 8 ++-- .../dto/CancelledParticipantDetail.java | 22 ++++++++++ ...=> CancelledParticipantsSearchResult.java} | 13 +++--- .../acceptance/AcceptanceFixtures.java | 8 ++-- .../acceptance/ScheduleAcceptanceTest.java | 6 +-- ...heduleParticipationDtoServiceImplTest.java | 31 +------------ ...ScheduleParticipationQueryUseCaseTest.java | 25 ++++++++++- 11 files changed, 101 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelledParticipantListResponse.java create mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantDetail.java rename src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/{CancelledParticipantList.java => CancelledParticipantsSearchResult.java} (60%) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index be4dca4f..4305620b 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -6,7 +6,8 @@ import project.volunteer.domain.scheduleParticipation.api.dto.*; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandFacade; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryFacade; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.global.Interceptor.OrganizationAuth; @@ -63,10 +64,9 @@ public ResponseEntity scheduleParticipantL @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancelling") - public ResponseEntity scheduleCancelledParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - - List cancellingParticipants = scheduleParticipationQueryFacade.findCancelledParticipantsSchedule(scheduleNo); - return ResponseEntity.ok(new CancelledParticipantListResponse(cancellingParticipants)); + public ResponseEntity scheduleCancelledParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + CancelledParticipantsSearchResult result = scheduleParticipationQueryFacade.findCancelledParticipants(scheduleNo); + return ResponseEntity.ok(result); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelledParticipantListResponse.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelledParticipantListResponse.java deleted file mode 100644 index 3f9e0666..00000000 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelledParticipantListResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.api.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; - -import java.util.List; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -public class CancelledParticipantListResponse { - List cancelling; -} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java index ef2ad244..46c47140 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; @@ -22,17 +22,17 @@ public List findScheduleParticipatingList(final Lo return scheduleParticipationDtoService.searchParticipatingList(schedule.getScheduleNo()); } + public CancelledParticipantsSearchResult findCancelledParticipants(Long scheduleNo){ + Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); + return scheduleParticipationDtoService.searchCancelledParticipationList(schedule.getScheduleNo()); + } - public List findCancelledParticipantsSchedule(Long scheduleNo){ - Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - return scheduleParticipationDtoService.findCancelledParticipants(schedule); - } public List findCompletedParticipantsSchedule(Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java index de699d16..7d0ef869 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java @@ -4,7 +4,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; @@ -31,6 +32,17 @@ public List searchParticipatingList(final Long sch .collect(Collectors.toList()); } + @Override + public CancelledParticipantsSearchResult searchCancelledParticipationList(final Long scheduleNo) { + final List states = List.of(ParticipantState.PARTICIPATION_CANCEL); + final List cancelledParticipantDetails = scheduleParticipationRepository.findScheduleParticipationDetailBy( + scheduleNo, states) + .stream() + .map(CancelledParticipantDetail::from) + .collect(Collectors.toList()); + + return new CancelledParticipantsSearchResult(cancelledParticipantDetails); + } @@ -39,32 +51,29 @@ public List searchParticipatingList(final Long sch - @Override - public List findCancelledParticipants(Schedule schedule) { - return scheduleParticipationRepository.findOptimizationParticipantByScheduleAndState(schedule.getScheduleNo(), List.of(ParticipantState.PARTICIPATION_CANCEL)).stream() - .map(p -> new CancelledParticipantList(p.getScheduleParticipationNo(), p.getNickname(), p.getEmail(), p.getProfile())) - .collect(Collectors.toList()); - } @Override public List findCompletedParticipants(Schedule schedule) { return scheduleParticipationRepository.findOptimizationParticipantByScheduleAndState(schedule.getScheduleNo(), - List.of(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)).stream() + List.of(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL, + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)).stream() .map(sp -> { - StateResult state = sp.isEqualParticipantState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL)?(StateResult.COMPLETE_APPROVED):(StateResult.COMPLETE_UNAPPROVED); - return new CompletedParticipantList(sp.getScheduleParticipationNo(), sp.getNickname(), sp.getEmail(), sp.getProfile(), state.getId()); + StateResult state = sp.isEqualParticipantState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL) + ? (StateResult.COMPLETE_APPROVED) : (StateResult.COMPLETE_UNAPPROVED); + return new CompletedParticipantList(sp.getScheduleParticipationNo(), sp.getNickname(), + sp.getEmail(), sp.getProfile(), state.getId()); }) .collect(Collectors.toList()); } @Override public List findCompleteScheduleList(Long loginUserNo, ParticipantState state) { - return scheduleParticipationRepository.findCompletedSchedules(loginUserNo, state).stream() - .map(cs -> { - return new ParsingCompleteSchedule(cs.getScheduleNo() - , cs.getRecruitmentTitle() - , cs.getEndDay().format(DateTimeFormatter.ofPattern("MM-dd-yyyy"))); - }) - .collect(Collectors.toList()); + return scheduleParticipationRepository.findCompletedSchedules(loginUserNo, state).stream() + .map(cs -> { + return new ParsingCompleteSchedule(cs.getScheduleNo() + , cs.getRecruitmentTitle() + , cs.getEndDay().format(DateTimeFormatter.ofPattern("MM-dd-yyyy"))); + }) + .collect(Collectors.toList()); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java index 62d16e18..29f84b69 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java @@ -1,6 +1,6 @@ package project.volunteer.domain.scheduleParticipation.service; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; @@ -12,12 +12,14 @@ public interface ScheduleParticipationQueryUseCase { List searchParticipatingList(Long scheduleNo); + CancelledParticipantsSearchResult searchCancelledParticipationList(Long scheduleNo); + + + - //취소 요청 참가자 리스트 조회 - public List findCancelledParticipants(Schedule schedule); //참여 완료된(참여 미승인, 참여 승인) 참가자 리스트 조회 public List findCompletedParticipants(Schedule schedule); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantDetail.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantDetail.java new file mode 100644 index 00000000..b24c3c99 --- /dev/null +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantDetail.java @@ -0,0 +1,22 @@ +package project.volunteer.domain.scheduleParticipation.service.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class CancelledParticipantDetail { + private Long scheduleParticipationNo; + private String nickname; + private String email; + private String profile; + + public static CancelledParticipantDetail from(ScheduleParticipationDetail scheduleParticipationDetail) { + return new CancelledParticipantDetail(scheduleParticipationDetail.getScheduleParticipationNo(), + scheduleParticipationDetail.getNickname(), scheduleParticipationDetail.getEmail(), + scheduleParticipationDetail.getProfile()); + } +} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantList.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantsSearchResult.java similarity index 60% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantList.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantsSearchResult.java index bc0a8b8f..3f0b5768 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantList.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CancelledParticipantsSearchResult.java @@ -1,15 +1,14 @@ package project.volunteer.domain.scheduleParticipation.service.dto; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -@AllArgsConstructor -@NoArgsConstructor @Getter -public class CancelledParticipantList { - Long scheduleParticipationNo; - String nickname; - String email; - String profile; +@NoArgsConstructor +@AllArgsConstructor +public class CancelledParticipantsSearchResult { + private List cancelling; + } diff --git a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java index 416ea196..4f5bcbe9 100644 --- a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java +++ b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java @@ -19,10 +19,10 @@ import project.volunteer.domain.recruitment.domain.repeatPeriod.Period; import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; -import project.volunteer.domain.scheduleParticipation.api.dto.CancelledParticipantListResponse; import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; import project.volunteer.domain.scheduleParticipation.api.dto.CompletedParticipantListResponse; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.domain.sehedule.api.dto.response.ScheduleCalenderSearchResponse; @@ -170,7 +170,7 @@ public class AcceptanceFixtures { .getDone(); } - public static List 봉사_일정_취소요청_조회(String token, Long recruitmentNo, Long scheduleNo) { + public static List 봉사_일정_취소요청_조회(String token, Long recruitmentNo, Long scheduleNo) { return given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header(AUTHORIZATION_HEADER, token) @@ -178,7 +178,7 @@ public class AcceptanceFixtures { .then().log().all() .statusCode(HttpStatus.OK.value()) .extract() - .as(CancelledParticipantListResponse.class) + .as(CancelledParticipantsSearchResult.class) .getCancelling(); } diff --git a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java index de99d537..dc2db711 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java @@ -32,7 +32,7 @@ import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; @@ -756,12 +756,12 @@ void findScheduleAvailableStateAfterApprovalCancel() { 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo2); - final List cancelledParticipants = + final List cancelledParticipants = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, scheduleNo2); final CancelApproval cancelApprovalRequest = new CancelApproval( cancelledParticipants.stream() - .map(CancelledParticipantList::getScheduleParticipationNo) + .map(CancelledParticipantDetail::getScheduleParticipationNo) .collect(Collectors.toList())); 봉사_일정_참여_취소승인(bonsikToken, recruitmentNo, scheduleNo2, cancelApprovalRequest); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java index 1543c64b..21902cda 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java @@ -15,9 +15,8 @@ import project.volunteer.domain.recruitment.repository.RecruitmentRepository; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.repository.ScheduleRepository; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.user.dao.UserRepository; @@ -84,34 +83,6 @@ void init(){ saveSchedule = scheduleRepository.save(createSchedule); } - @Test - @DisplayName("일정 취소 요청 리스트 조회 시 2명의 취소 요청자가 조회된다.") - public void schedule_cancel_participant_list(){ - //given - User newUser1 = 사용자_등록("rnqhstlr"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATING); - - User newUser2 = 사용자_등록("didthdms"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_CANCEL); - - User newUser3 = 사용자_등록("didghfhr"); - RecruitmentParticipation newParticipant3 = 봉사모집글_팀원_등록(saveRecruitment, newUser3); - 일정_참여자_상태_추가(saveSchedule, newParticipant3, ParticipantState.PARTICIPATION_CANCEL); - clear(); - - //when - List findCancellingParticipants = spDtoService.findCancelledParticipants(saveSchedule); - - - //then - assertThat(findCancellingParticipants.size()).isEqualTo(2); - for(CancelledParticipantList p : findCancellingParticipants){ - assertThat(p.getProfile()).isEqualTo("picture"); - } - } - @Test @DisplayName("일정 참여 완료 리스트 조회 시 2명의 참여 완료 미승인과 1명의 참여 완료 승인자가 조회된다.") public void schedule_completed_participant_list(){ diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java index de3dcd21..799653e4 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java @@ -1,5 +1,6 @@ package project.volunteer.domain.scheduleParticipation.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import java.time.LocalDate; @@ -15,6 +16,7 @@ import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.user.domain.Gender; @@ -95,7 +97,7 @@ void searchParticipantList() { schedule.getScheduleNo()); //then - Assertions.assertThat(participatingParticipantLists).hasSize(2) + assertThat(participatingParticipantLists).hasSize(2) .extracting("nickname", "email", "profile") .containsExactlyInAnyOrder( tuple(firstUser.getNickName(), firstUser.getEmail(), firstUser.getPicture()), @@ -103,4 +105,25 @@ void searchParticipantList() { ); } + @DisplayName("일정 참여를 취소한 리스트를 조회한다.") + @Test + void searchCancelledParticipantList() { + //given + final ScheduleParticipation scheduleParticipation1 = new ScheduleParticipation(schedule, + firstRecruitmentParticipation, ParticipantState.PARTICIPATION_CANCEL); + final ScheduleParticipation scheduleParticipation2 = new ScheduleParticipation(schedule, + secondRecruitmentParticipation, ParticipantState.PARTICIPATION_CANCEL); + + scheduleParticipationRepository.saveAll(List.of(scheduleParticipation1, scheduleParticipation2)); + + //when + CancelledParticipantsSearchResult result = scheduleParticipationQueryUseCase.searchCancelledParticipationList( + schedule.getScheduleNo()); + + //then + assertThat(result.getCancelling()).hasSize(2) + .extracting("scheduleParticipationNo") + .containsExactlyInAnyOrder(scheduleParticipation1.getId(), scheduleParticipation2.getId()); + } + } \ No newline at end of file From e624ea4143c456a77e8a4429b379be24835dc200 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 18:04:27 +0900 Subject: [PATCH 15/29] =?UTF-8?q?refactor:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=A4=91=20=EC=9D=B8=EC=9B=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20response=20DTO=20=EB=B0=98=ED=99=98=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 11 +++++------ .../dto/ParticipatingParticipantListResponse.java | 15 --------------- .../service/ScheduleParticipationQueryFacade.java | 6 +++--- .../ScheduleParticipationQueryService.java | 12 ++++++++---- .../ScheduleParticipationQueryUseCase.java | 4 ++-- ...pantList.java => ActiveParticipantDetail.java} | 6 +++--- .../dto/ActiveParticipantSearchResult.java | 13 +++++++++++++ .../ScheduleParticipationQueryUseCaseTest.java | 8 ++++---- 8 files changed, 38 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipatingParticipantListResponse.java rename src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/{ParticipatingParticipantList.java => ActiveParticipantDetail.java} (61%) create mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index 4305620b..cc4df1c6 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -6,10 +6,10 @@ import project.volunteer.domain.scheduleParticipation.api.dto.*; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandFacade; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryFacade; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; import project.volunteer.global.Interceptor.OrganizationAuth; import project.volunteer.global.util.SecurityUtil; @@ -56,10 +56,9 @@ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/participating") - public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - - List participating = scheduleParticipationQueryFacade.findScheduleParticipatingList(scheduleNo); - return ResponseEntity.ok(new ParticipatingParticipantListResponse(participating)); + public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + ActiveParticipantSearchResult result = scheduleParticipationQueryFacade.findActiveParticipants(scheduleNo); + return ResponseEntity.ok(result); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipatingParticipantListResponse.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipatingParticipantListResponse.java deleted file mode 100644 index afeacecc..00000000 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipatingParticipantListResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.api.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; - -import java.util.List; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -public class ParticipatingParticipantListResponse { - List participating; -} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java index 46c47140..ed0cfa98 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java @@ -4,9 +4,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; import project.volunteer.domain.sehedule.domain.Schedule; @@ -17,9 +17,9 @@ public class ScheduleParticipationQueryFacade { private final ScheduleQueryUseCase scheduleQueryUsecase; private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; - public List findScheduleParticipatingList(final Long scheduleNo){ + public ActiveParticipantSearchResult findActiveParticipants(final Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - return scheduleParticipationDtoService.searchParticipatingList(schedule.getScheduleNo()); + return scheduleParticipationDtoService.searchActiveParticipationList(schedule.getScheduleNo()); } public CancelledParticipantsSearchResult findCancelledParticipants(Long scheduleNo){ diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java index 7d0ef869..8413084d 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java @@ -4,11 +4,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; import project.volunteer.global.common.dto.StateResult; @@ -24,12 +25,15 @@ public class ScheduleParticipationQueryService implements ScheduleParticipationQ private final ScheduleParticipationRepository scheduleParticipationRepository; @Override - public List searchParticipatingList(final Long scheduleNo) { + public ActiveParticipantSearchResult searchActiveParticipationList(final Long scheduleNo) { final List states = List.of(ParticipantState.PARTICIPATING); - return scheduleParticipationRepository.findScheduleParticipationDetailBy(scheduleNo, states) + final List activeParticipantDetails = scheduleParticipationRepository.findScheduleParticipationDetailBy( + scheduleNo, states) .stream() - .map(ParticipatingParticipantList::from) + .map(ActiveParticipantDetail::from) .collect(Collectors.toList()); + + return new ActiveParticipantSearchResult(activeParticipantDetails); } @Override diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java index 29f84b69..d1141b30 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java @@ -1,16 +1,16 @@ package project.volunteer.domain.scheduleParticipation.service; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; import java.util.List; public interface ScheduleParticipationQueryUseCase { - List searchParticipatingList(Long scheduleNo); + ActiveParticipantSearchResult searchActiveParticipationList(Long scheduleNo); CancelledParticipantsSearchResult searchCancelledParticipationList(Long scheduleNo); diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantDetail.java similarity index 61% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantDetail.java index a002a2f9..1136a7e4 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ParticipatingParticipantList.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantDetail.java @@ -8,13 +8,13 @@ @AllArgsConstructor @NoArgsConstructor @Getter -public class ParticipatingParticipantList { +public class ActiveParticipantDetail { private String nickname; private String email; private String profile; - public static ParticipatingParticipantList from(ScheduleParticipationDetail detail) { - return new ParticipatingParticipantList(detail.getNickname(), detail.getEmail(), detail.getProfile()); + public static ActiveParticipantDetail from(ScheduleParticipationDetail detail) { + return new ActiveParticipantDetail(detail.getNickname(), detail.getEmail(), detail.getProfile()); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java new file mode 100644 index 00000000..079f54f6 --- /dev/null +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java @@ -0,0 +1,13 @@ +package project.volunteer.domain.scheduleParticipation.service.dto; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ActiveParticipantSearchResult { + private List participating; +} diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java index 799653e4..5a71ebd3 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java @@ -6,7 +6,6 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.List; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,8 +15,9 @@ import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.ParticipatingParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.user.domain.Gender; import project.volunteer.domain.user.domain.Role; @@ -93,11 +93,11 @@ void searchParticipantList() { scheduleParticipationRepository.saveAll(List.of(scheduleParticipation1, scheduleParticipation2)); //when - List participatingParticipantLists = scheduleParticipationQueryUseCase.searchParticipatingList( + ActiveParticipantSearchResult result = scheduleParticipationQueryUseCase.searchActiveParticipationList( schedule.getScheduleNo()); //then - assertThat(participatingParticipantLists).hasSize(2) + assertThat(result.getParticipating()).hasSize(2) .extracting("nickname", "email", "profile") .containsExactlyInAnyOrder( tuple(firstUser.getNickName(), firstUser.getEmail(), firstUser.getPicture()), From ddd6c5b102510afea99bd38ba7106aa04cace1cc Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 18:32:42 +0900 Subject: [PATCH 16/29] =?UTF-8?q?refactor:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EC=99=84=EB=A3=8C=20=EC=9D=B8=EC=9B=90=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20UseCase=20=EC=BF=BC=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20DTO=20=EC=88=98=EC=A0=95=EA=B3=BC=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/ScheduleParticipantController.java | 16 +- .../dto/CompletedParticipantListResponse.java | 15 -- .../ScheduleParticipationQueryFacade.java | 21 +- .../ScheduleParticipationQueryService.java | 35 ++- .../ScheduleParticipationQueryUseCase.java | 17 +- ...va => ActiveParticipantsSearchResult.java} | 2 +- .../dto/CompletedParticipantDetail.java | 26 ++ ...=> CompletedParticipantsSearchResult.java} | 14 +- .../common/component/ParticipantState.java | 7 + .../acceptance/AcceptanceFixtures.java | 8 +- .../acceptance/ScheduleAcceptanceTest.java | 6 +- ...heduleParticipationDtoServiceImplTest.java | 232 ------------------ ...ScheduleParticipationQueryUseCaseTest.java | 30 ++- 13 files changed, 106 insertions(+), 323 deletions(-) delete mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompletedParticipantListResponse.java rename src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/{ActiveParticipantSearchResult.java => ActiveParticipantsSearchResult.java} (86%) create mode 100644 src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantDetail.java rename src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/{CompletedParticipantList.java => CompletedParticipantsSearchResult.java} (53%) delete mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java index cc4df1c6..255b7df0 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java @@ -6,10 +6,10 @@ import project.volunteer.domain.scheduleParticipation.api.dto.*; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandFacade; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryFacade; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.global.Interceptor.OrganizationAuth; import project.volunteer.global.util.SecurityUtil; @@ -56,8 +56,8 @@ public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/participating") - public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - ActiveParticipantSearchResult result = scheduleParticipationQueryFacade.findActiveParticipants(scheduleNo); + public ResponseEntity scheduleParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + ActiveParticipantsSearchResult result = scheduleParticipationQueryFacade.findActiveParticipants(scheduleNo); return ResponseEntity.ok(result); } @@ -70,9 +70,9 @@ public ResponseEntity scheduleCancelledPartic @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @GetMapping("/{recruitmentNo}/schedule/{scheduleNo}/completion") - public ResponseEntity scheduleCompletedParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + public ResponseEntity scheduleCompletedParticipantList(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ - List completedParticipants = scheduleParticipationQueryFacade.findCompletedParticipantsSchedule(scheduleNo); - return ResponseEntity.ok(new CompletedParticipantListResponse(completedParticipants)); + CompletedParticipantsSearchResult result = scheduleParticipationQueryFacade.findCompletedParticipants(scheduleNo); + return ResponseEntity.ok(result); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompletedParticipantListResponse.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompletedParticipantListResponse.java deleted file mode 100644 index 16961d6b..00000000 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompletedParticipantListResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.api.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; - -import java.util.List; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -public class CompletedParticipantListResponse { - List done; -} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java index ed0cfa98..a190d74e 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryFacade.java @@ -1,12 +1,11 @@ package project.volunteer.domain.scheduleParticipation.service; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.sehedule.application.ScheduleQueryUseCase; import project.volunteer.domain.sehedule.domain.Schedule; @@ -17,27 +16,19 @@ public class ScheduleParticipationQueryFacade { private final ScheduleQueryUseCase scheduleQueryUsecase; private final ScheduleParticipationQueryUseCase scheduleParticipationDtoService; - public ActiveParticipantSearchResult findActiveParticipants(final Long scheduleNo){ + public ActiveParticipantsSearchResult findActiveParticipants(final Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); return scheduleParticipationDtoService.searchActiveParticipationList(schedule.getScheduleNo()); } - public CancelledParticipantsSearchResult findCancelledParticipants(Long scheduleNo){ + public CancelledParticipantsSearchResult findCancelledParticipants(final Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); return scheduleParticipationDtoService.searchCancelledParticipationList(schedule.getScheduleNo()); } - - - - - - - - public List findCompletedParticipantsSchedule(Long scheduleNo){ + public CompletedParticipantsSearchResult findCompletedParticipants(final Long scheduleNo){ Schedule schedule = scheduleQueryUsecase.findActivitedSchedule(scheduleNo); - - return scheduleParticipationDtoService.findCompletedParticipants(schedule); + return scheduleParticipationDtoService.searchCompletedParticipationList(schedule.getScheduleNo()); } } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java index 8413084d..007b934e 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryService.java @@ -4,15 +4,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; -import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; -import project.volunteer.global.common.dto.StateResult; import java.time.format.DateTimeFormatter; import java.util.List; @@ -25,7 +24,7 @@ public class ScheduleParticipationQueryService implements ScheduleParticipationQ private final ScheduleParticipationRepository scheduleParticipationRepository; @Override - public ActiveParticipantSearchResult searchActiveParticipationList(final Long scheduleNo) { + public ActiveParticipantsSearchResult searchActiveParticipationList(final Long scheduleNo) { final List states = List.of(ParticipantState.PARTICIPATING); final List activeParticipantDetails = scheduleParticipationRepository.findScheduleParticipationDetailBy( scheduleNo, states) @@ -33,7 +32,7 @@ public ActiveParticipantSearchResult searchActiveParticipationList(final Long sc .map(ActiveParticipantDetail::from) .collect(Collectors.toList()); - return new ActiveParticipantSearchResult(activeParticipantDetails); + return new ActiveParticipantsSearchResult(activeParticipantDetails); } @Override @@ -48,7 +47,17 @@ public CancelledParticipantsSearchResult searchCancelledParticipationList(final return new CancelledParticipantsSearchResult(cancelledParticipantDetails); } + @Override + public CompletedParticipantsSearchResult searchCompletedParticipationList(final Long scheduleNo) { + final List states = ParticipantState.getParticipationCompletionState(); + final List completedParticipantDetails = scheduleParticipationRepository.findScheduleParticipationDetailBy( + scheduleNo, states) + .stream() + .map(CompletedParticipantDetail::from) + .collect(Collectors.toList()); + return new CompletedParticipantsSearchResult(completedParticipantDetails); + } @@ -56,20 +65,6 @@ public CancelledParticipantsSearchResult searchCancelledParticipationList(final - @Override - public List findCompletedParticipants(Schedule schedule) { - return scheduleParticipationRepository.findOptimizationParticipantByScheduleAndState(schedule.getScheduleNo(), - List.of(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL, - ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)).stream() - .map(sp -> { - StateResult state = sp.isEqualParticipantState(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL) - ? (StateResult.COMPLETE_APPROVED) : (StateResult.COMPLETE_UNAPPROVED); - return new CompletedParticipantList(sp.getScheduleParticipationNo(), sp.getNickname(), - sp.getEmail(), sp.getProfile(), state.getId()); - }) - .collect(Collectors.toList()); - } - @Override public List findCompleteScheduleList(Long loginUserNo, ParticipantState state) { return scheduleParticipationRepository.findCompletedSchedules(loginUserNo, state).stream() diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java index d1141b30..cb6eca4a 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCase.java @@ -1,30 +1,23 @@ package project.volunteer.domain.scheduleParticipation.service; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.ParsingCompleteSchedule; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; import java.util.List; public interface ScheduleParticipationQueryUseCase { - ActiveParticipantSearchResult searchActiveParticipationList(Long scheduleNo); + ActiveParticipantsSearchResult searchActiveParticipationList(Long scheduleNo); CancelledParticipantsSearchResult searchCancelledParticipationList(Long scheduleNo); + // 참여 완료 미승인 and 참여 완료 승인 + CompletedParticipantsSearchResult searchCompletedParticipationList(Long scheduleNo); - - - - - //참여 완료된(참여 미승인, 참여 승인) 참가자 리스트 조회 - public List findCompletedParticipants(Schedule schedule); - - // 특정 유저의 참여 승인된 스케줄 리스트 조회 public List findCompleteScheduleList(Long loginUserNo, ParticipantState state); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantsSearchResult.java similarity index 86% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantsSearchResult.java index 079f54f6..f1eb0318 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantSearchResult.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/ActiveParticipantsSearchResult.java @@ -8,6 +8,6 @@ @Getter @NoArgsConstructor @AllArgsConstructor -public class ActiveParticipantSearchResult { +public class ActiveParticipantsSearchResult { private List participating; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantDetail.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantDetail.java new file mode 100644 index 00000000..0cbe5925 --- /dev/null +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantDetail.java @@ -0,0 +1,26 @@ +package project.volunteer.domain.scheduleParticipation.service.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; +import project.volunteer.global.common.component.ParticipantState; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class CompletedParticipantDetail { + private Long scheduleParticipationNo; + private String nickname; + private String email; + private String profile; + private Boolean isApproved; // 참여 완료 승인 여부 + + public static CompletedParticipantDetail from(ScheduleParticipationDetail detail) { + Boolean isApproved = detail.getState().equals(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); + return new CompletedParticipantDetail(detail.getScheduleParticipationNo(), detail.getNickname(), + detail.getEmail(), detail.getProfile(), isApproved); + } + +} diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantList.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantsSearchResult.java similarity index 53% rename from src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantList.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantsSearchResult.java index 5ee8b176..d5ea36f0 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantList.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/dto/CompletedParticipantsSearchResult.java @@ -1,19 +1,13 @@ package project.volunteer.domain.scheduleParticipation.service.dto; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; +@Getter @AllArgsConstructor @NoArgsConstructor -@Getter -@Setter -public class CompletedParticipantList { - - Long scheduleParticipationNo; - String nickname; - String email; - String profile; - String status; +public class CompletedParticipantsSearchResult { + private List done; } diff --git a/src/main/java/project/volunteer/global/common/component/ParticipantState.java b/src/main/java/project/volunteer/global/common/component/ParticipantState.java index 43065119..070e744a 100644 --- a/src/main/java/project/volunteer/global/common/component/ParticipantState.java +++ b/src/main/java/project/volunteer/global/common/component/ParticipantState.java @@ -1,5 +1,7 @@ package project.volunteer.global.common.component; +import java.util.Arrays; +import java.util.List; import project.volunteer.global.common.converter.CodeCommonType; public enum ParticipantState implements CodeCommonType { @@ -36,4 +38,9 @@ public String getId() { public String getDesc() { return this.desc; } + + public static List getParticipationCompletionState() { + return List.of(ParticipantState.PARTICIPATION_COMPLETE_APPROVAL, + ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); + } } diff --git a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java index 4f5bcbe9..e0a5821c 100644 --- a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java +++ b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java @@ -20,10 +20,10 @@ import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; -import project.volunteer.domain.scheduleParticipation.api.dto.CompletedParticipantListResponse; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.domain.sehedule.api.dto.response.ScheduleCalenderSearchResponse; import project.volunteer.domain.sehedule.api.dto.response.ScheduleCalenderSearchResponses; @@ -158,7 +158,7 @@ public class AcceptanceFixtures { .extract(); } - public static List 봉사_일정_참여완료_조회(String token, Long recruitmentNo, Long scheduleNo) { + public static List 봉사_일정_참여완료_조회(String token, Long recruitmentNo, Long scheduleNo) { return given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header(AUTHORIZATION_HEADER, token) @@ -166,7 +166,7 @@ public class AcceptanceFixtures { .then().log().all() .statusCode(HttpStatus.OK.value()) .extract() - .as(CompletedParticipantListResponse.class) + .as(CompletedParticipantsSearchResult.class) .getDone(); } diff --git a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java index dc2db711..8052c2bf 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java @@ -33,7 +33,7 @@ import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.domain.sehedule.api.dto.response.ScheduleCalenderSearchResponse; @@ -654,12 +654,12 @@ void findScheduleCompleteApprovedState() { given(clock.instant()).willReturn(Instant.parse("2024-02-10T10:00:00Z")); 봉사_일정_참여완료_미승인_스케줄링(); - final List completedScheduleParticipants = + final List completedScheduleParticipants = 봉사_일정_참여완료_조회(bonsikToken, recruitmentNo, scheduleNo2); final CompleteApproval completeApprovalRequest = new CompleteApproval( completedScheduleParticipants.stream() - .map(CompletedParticipantList::getScheduleParticipationNo) + .map(CompletedParticipantDetail::getScheduleParticipationNo) .collect(Collectors.toList()) ); 봉사_일정_참여완료_승인(bonsikToken, recruitmentNo, scheduleNo2, completeApprovalRequest); diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java deleted file mode 100644 index 21902cda..00000000 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationDtoServiceImplTest.java +++ /dev/null @@ -1,232 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; -import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.domain.recruitment.domain.Recruitment; -import project.volunteer.domain.recruitment.domain.VolunteerType; -import project.volunteer.domain.recruitment.domain.VolunteeringCategory; -import project.volunteer.domain.recruitment.domain.VolunteeringType; -import project.volunteer.domain.recruitment.repository.RecruitmentRepository; -import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantList; -import project.volunteer.domain.sehedule.repository.ScheduleRepository; -import project.volunteer.domain.sehedule.domain.Schedule; -import project.volunteer.domain.user.dao.UserRepository; -import project.volunteer.domain.user.domain.Gender; -import project.volunteer.domain.user.domain.Role; -import project.volunteer.domain.user.domain.User; -import project.volunteer.global.common.component.*; -import project.volunteer.global.common.dto.StateResult; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -@SpringBootTest -@Transactional -class ScheduleParticipationDtoServiceImplTest { - - @PersistenceContext - EntityManager em; - @Autowired - UserRepository userRepository; - @Autowired RecruitmentRepository recruitmentRepository; - @Autowired - ScheduleRepository scheduleRepository; - @Autowired - RecruitmentParticipationRepository participantRepository; - @Autowired - ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired - ScheduleParticipationQueryUseCase spDtoService; - - private User writer; - private Recruitment saveRecruitment; - private Schedule saveSchedule; - @BeforeEach - void init(){ - //작성자 저장 - User writerUser = User.createUser("1234", "1234", "1234", "1234", Gender.M, LocalDate.now(), "1234", - true, true, true, Role.USER, "kakao", "1234", null); - writer = userRepository.save(writerUser); - - //모집글 저장 - Recruitment createRecruitment = new Recruitment( "title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.REG, - VolunteerType.ADULT, 9999,0,true, "unicef", - new Address("111", "11", "test", "test"), - new Coordinate(1.2F, 2.2F), - new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 3, 3), HourFormat.AM, - LocalTime.now(), 10), - 0, 0, true, IsDeleted.N, writerUser); - saveRecruitment = recruitmentRepository.save(createRecruitment); - - //일정 저장 - Schedule createSchedule = Schedule.create( - saveRecruitment, - new Timetable( - LocalDate.now().plusMonths(1), LocalDate.now().plusMonths(1), - HourFormat.AM, LocalTime.now(), 3), - "content", "organization", - Address.createAddress("11", "1111", "details", "fullName"), 3); - saveSchedule = scheduleRepository.save(createSchedule); - } - - @Test - @DisplayName("일정 참여 완료 리스트 조회 시 2명의 참여 완료 미승인과 1명의 참여 완료 승인자가 조회된다.") - public void schedule_completed_participant_list(){ - //given - User newUser1 = 사용자_등록("ku"); - RecruitmentParticipation newParticipant1 = 봉사모집글_팀원_등록(saveRecruitment, newUser1); - 일정_참여자_상태_추가(saveSchedule, newParticipant1, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - User newUser2 = 사용자_등록("yangsoeun"); - RecruitmentParticipation newParticipant2 = 봉사모집글_팀원_등록(saveRecruitment, newUser2); - 일정_참여자_상태_추가(saveSchedule, newParticipant2, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - User newUser3 = 사용자_등록("yanghorok"); - RecruitmentParticipation newParticipant3 = 봉사모집글_팀원_등록(saveRecruitment, newUser3); - 일정_참여자_상태_추가(saveSchedule, newParticipant3, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); - clear(); - - //when - List findCompletedParticipants = spDtoService.findCompletedParticipants(saveSchedule); - - //then - assertThat(findCompletedParticipants.size()).isEqualTo(3); - for(CompletedParticipantList p : findCompletedParticipants){ - assertThat(p.getProfile()).isEqualTo("picture"); - assertThat(p.getStatus()).isIn(StateResult.COMPLETE_APPROVED.name(), StateResult.COMPLETE_UNAPPROVED.name()); - } - } - - //TODO: 봉사 참여자 관리 리팩토링 시 수정할 예정 -// @Test -// @DisplayName("첫 일정 참가이므로 available 상태가 된다.") -// public void availableStateByFirstParticipation(){ -// //given -// Schedule schedule1 = 스케줄_등록(LocalDate.now().plusDays(1), 3); -// 스케줄_참여자_등록(schedule1, teamMembers.get(0), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(1), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// -// //when -// ScheduleDetails closestSchedule = scheduleDtoService.findClosestSchedule(saveRecruitment.getRecruitmentNo(), -// teamMembers.get(2).getParticipant().getUserNo()); -// -// //then -// Assertions.assertThat(closestSchedule.getState()).isEqualTo(StateResponse.AVAILABLE.name()); -// } -// -// @Test -// @Transactional -// @DisplayName("최근 신청 상태가 일정 취소 승인이므로 available 상태가 된다.") -// public void availableStateByCancelApprove(){ -// //given -// Schedule schedule1 = 스케줄_등록(LocalDate.now().plusMonths(3), 3); -// 스케줄_참여자_등록(schedule1, teamMembers.get(0), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(1), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(2), ParticipantState.PARTICIPATION_CANCEL_APPROVAL); -// -// //when -// ScheduleDetails closestSchedule = scheduleDtoService.findClosestSchedule(saveRecruitment.getRecruitmentNo(), -// teamMembers.get(2).getParticipant().getUserNo()); -// -// //then -// Assertions.assertThat(closestSchedule.getState()).isEqualTo(StateResponse.AVAILABLE.name()); -// } -// -// @Test -// @Transactional -// @DisplayName("최근 신청 상태가 일정 취소 신청이므로 cancelling 상태가 된다.") -// public void cancellingState(){ -// //given -// Schedule schedule1 = 스케줄_등록(LocalDate.now().plusMonths(3), 3); -// 스케줄_참여자_등록(schedule1, teamMembers.get(0), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(1), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(2), ParticipantState.PARTICIPATION_CANCEL); -// schedule1.increaseParticipant(); -// -// //when -// ScheduleDetails closestSchedule = scheduleDtoService.findClosestSchedule(saveRecruitment.getRecruitmentNo(), -// teamMembers.get(2).getParticipant().getUserNo()); -// -// //then -// Assertions.assertThat(closestSchedule.getState()).isEqualTo(StateResponse.CANCELLING.name()); -// } -// -// @Test -// @DisplayName("최근 신청 상태가 참여 승인 이므로 participating 상태가 된다.") -// @Transactional -// public void participatingState(){ -// //given -// Schedule schedule1 = 스케줄_등록(LocalDate.now().plusMonths(3), 3); -// 스케줄_참여자_등록(schedule1, teamMembers.get(0), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(1), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(2), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// -// //when -// ScheduleDetails closestSchedule = scheduleDtoService.findClosestSchedule(saveRecruitment.getRecruitmentNo(), -// teamMembers.get(2).getParticipant().getUserNo()); -// -// //then -// Assertions.assertThat(closestSchedule.getState()).isEqualTo(StateResponse.PARTICIPATING.name()); -// } -// @Test -// @Transactional -// @DisplayName("일정 최대 참가자 수를 초과하여 FULL 상태가 된다.") -// public void doneState(){ -// //given -// Schedule schedule1 = 스케줄_등록(LocalDate.now().plusMonths(3), 3); -// 스케줄_참여자_등록(schedule1, teamMembers.get(0), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(1), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// 스케줄_참여자_등록(schedule1, teamMembers.get(2), ParticipantState.PARTICIPATING); -// schedule1.increaseParticipant(); -// -// //when -// ScheduleDetails closestSchedule = scheduleDtoService.findClosestSchedule(saveRecruitment.getRecruitmentNo(), -// teamMembers.get(3).getParticipant().getUserNo()); -// -// //then -// Assertions.assertThat(closestSchedule.getState()).isEqualTo(StateResponse.FULL.name()); -// } - - private User 사용자_등록(String username){ - User createUser = User.createUser(username, username, username, username, Gender.M, LocalDate.now(), "picture", - true, true, true, Role.USER, "kakao", username, null); - return userRepository.save(createUser); - } - private RecruitmentParticipation 봉사모집글_팀원_등록(Recruitment recruitment, User user){ - RecruitmentParticipation participant = RecruitmentParticipation.createParticipant(recruitment, user, ParticipantState.JOIN_APPROVAL); - return participantRepository.save(participant); - } - private ScheduleParticipation 일정_참여자_상태_추가(Schedule schedule, RecruitmentParticipation participant, ParticipantState state){ - ScheduleParticipation scheduleParticipation = ScheduleParticipation.createScheduleParticipation(schedule, participant, state); - return scheduleParticipationRepository.save(scheduleParticipation); - } - private void clear() { - em.flush(); - em.clear(); - } -} \ No newline at end of file diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java index 5a71ebd3..bcb7c9c0 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationQueryUseCaseTest.java @@ -15,9 +15,9 @@ import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantSearchResult; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.user.domain.Gender; import project.volunteer.domain.user.domain.Role; @@ -93,7 +93,7 @@ void searchParticipantList() { scheduleParticipationRepository.saveAll(List.of(scheduleParticipation1, scheduleParticipation2)); //when - ActiveParticipantSearchResult result = scheduleParticipationQueryUseCase.searchActiveParticipationList( + ActiveParticipantsSearchResult result = scheduleParticipationQueryUseCase.searchActiveParticipationList( schedule.getScheduleNo()); //then @@ -126,4 +126,28 @@ void searchCancelledParticipantList() { .containsExactlyInAnyOrder(scheduleParticipation1.getId(), scheduleParticipation2.getId()); } + @DisplayName("일정 참여에 완료한 사용자들을 조회한다.") + @Test + void searchCompletedParticipantList() { + //given + final ScheduleParticipation scheduleParticipation1 = new ScheduleParticipation(schedule, + firstRecruitmentParticipation, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); + final ScheduleParticipation scheduleParticipation2 = new ScheduleParticipation(schedule, + secondRecruitmentParticipation, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); + + scheduleParticipationRepository.saveAll(List.of(scheduleParticipation1, scheduleParticipation2)); + + //when + CompletedParticipantsSearchResult result = scheduleParticipationQueryUseCase.searchCompletedParticipationList( + schedule.getScheduleNo()); + + //then + assertThat(result.getDone()).hasSize(2) + .extracting("scheduleParticipationNo", "isApproved") + .containsExactlyInAnyOrder( + tuple(scheduleParticipation1.getId(), false), + tuple(scheduleParticipation2.getId(), true) + ); + } + } \ No newline at end of file From da68fd6226fdf1500e39b45ad482a959c2818643 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 18:38:31 +0900 Subject: [PATCH 17/29] =?UTF-8?q?test:=20schedule=20=EC=9D=B8=EC=88=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=8C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/project/volunteer/acceptance/AcceptanceTest.java | 4 ++-- .../project/volunteer/acceptance/ScheduleAcceptanceTest.java | 4 ++++ .../service/ConcurrentScheduleParticipationTest.java | 4 ++-- .../support/{DatabaseCleaner.java => H2DatabaseCleaner.java} | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) rename src/test/java/project/volunteer/support/{DatabaseCleaner.java => H2DatabaseCleaner.java} (97%) diff --git a/src/test/java/project/volunteer/acceptance/AcceptanceTest.java b/src/test/java/project/volunteer/acceptance/AcceptanceTest.java index 35481f42..05e70987 100644 --- a/src/test/java/project/volunteer/acceptance/AcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/AcceptanceTest.java @@ -12,7 +12,7 @@ import project.volunteer.domain.signup.api.dto.request.UserSignupRequest; import project.volunteer.domain.signup.application.UserSignupService; import project.volunteer.global.jwt.util.JwtProvider; -import project.volunteer.support.DatabaseCleaner; +import project.volunteer.support.H2DatabaseCleaner; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @@ -27,7 +27,7 @@ public class AcceptanceTest { protected JwtProvider jwtProvider; @Autowired - protected DatabaseCleaner databaseCleaner; + protected H2DatabaseCleaner databaseCleaner; @SpyBean protected Clock clock; diff --git a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java index 8052c2bf..e0a74648 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java @@ -405,6 +405,8 @@ void findScheduleNotRecruitmentTeam() { @DisplayName("캘린더를 통한 일정 상세 조회시, 참여가 가능한 일정이면 state필드가 AVAILABLE로 나온다.") @Test void findScheduleAvailableState() { + given(clock.instant()).willReturn(Instant.parse("2024-02-01T10:00:00Z")); + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, VolunteerType.ADULT, 100, VolunteeringType.IRREG, "01-01-2024", "02-20-2024", HourFormat.AM, "10:00", @@ -447,6 +449,8 @@ void findScheduleAvailableState() { @DisplayName("캘린더를 통한 일정 상세 조회시, 모집 기간이 지난 일정이면 state필드가 DONE로 나온다.") @Test void findScheduleDoneState() { + given(clock.instant()).willReturn(Instant.parse("2024-02-02T10:00:00Z")); + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, VolunteerType.ADULT, 100, VolunteeringType.IRREG, "01-01-2024", "02-20-2024", HourFormat.AM, "10:00", diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java index 2508249e..dc669daa 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/service/ConcurrentScheduleParticipationTest.java @@ -44,7 +44,7 @@ import project.volunteer.global.common.component.Timetable; import project.volunteer.global.error.exception.BusinessException; import project.volunteer.global.error.exception.ErrorCode; -import project.volunteer.support.DatabaseCleaner; +import project.volunteer.support.H2DatabaseCleaner; @SpringBootTest @ActiveProfiles("test") @@ -64,7 +64,7 @@ public class ConcurrentScheduleParticipationTest { @SpyBean private Clock clock; @Autowired - private DatabaseCleaner databaseCleaner; + private H2DatabaseCleaner databaseCleaner; @AfterEach void tearDown() { diff --git a/src/test/java/project/volunteer/support/DatabaseCleaner.java b/src/test/java/project/volunteer/support/H2DatabaseCleaner.java similarity index 97% rename from src/test/java/project/volunteer/support/DatabaseCleaner.java rename to src/test/java/project/volunteer/support/H2DatabaseCleaner.java index c569b57c..a4b2c97b 100644 --- a/src/test/java/project/volunteer/support/DatabaseCleaner.java +++ b/src/test/java/project/volunteer/support/H2DatabaseCleaner.java @@ -11,7 +11,7 @@ import org.springframework.transaction.annotation.Transactional; @Component -public class DatabaseCleaner { +public class H2DatabaseCleaner { private static final String FOREIGN_KEY_CHECK_FORMAT = "SET REFERENTIAL_INTEGRITY %s"; private static final String TRUNCATE_TABLE_FORMAT = "TRUNCATE TABLE %s"; private List tableNames; From 63e8acec43bcbbd0d2b0064f4a6ea8802fd7d05a Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 18:42:23 +0900 Subject: [PATCH 18/29] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B0=8F=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationRepository.java | 17 ----------------- .../common/component/ParticipantState.java | 1 - 2 files changed, 18 deletions(-) diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java index fbd97d26..479263fa 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/repository/ScheduleParticipationRepository.java @@ -7,7 +7,6 @@ import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; import project.volunteer.domain.scheduleParticipation.repository.dto.CompletedScheduleDetail; -import project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.global.common.component.ParticipantState; @@ -68,22 +67,6 @@ Optional findByUserNoAndScheduleNoAndState(@Param("userNo @Param("scheduleNo") Long scheduleNo, @Param("state") ParticipantState state); - //N+1 문제를 막기 위해서 Projection + Join 방식 사용 - @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.ScheduleParticipationDetail(sp.id,u.nickName,u.email,coalesce(s.imagePath,u.picture),sp.state) " - + - "from ScheduleParticipation sp " + - "join sp.recruitmentParticipation p " + - "join p.user u " + - "left join Image i " + - "on i.no=u.userNo " + - "and i.realWorkCode=project.volunteer.global.common.component.RealWorkCode.USER " + - "and i.isDeleted=project.volunteer.global.common.component.IsDeleted.N " + - "left join i.storage s " + - "where sp.schedule.scheduleNo=:scheduleNo " + - "and sp.state in :states ") - List findOptimizationParticipantByScheduleAndState(@Param("scheduleNo") Long scheduleNo, - @Param("states") List states); - @Query("select new project.volunteer.domain.scheduleParticipation.repository.dto.CompletedScheduleDetail" + "(sp.schedule.scheduleNo " + ",sp.recruitmentParticipation.recruitment.title " + diff --git a/src/main/java/project/volunteer/global/common/component/ParticipantState.java b/src/main/java/project/volunteer/global/common/component/ParticipantState.java index 070e744a..778ccb30 100644 --- a/src/main/java/project/volunteer/global/common/component/ParticipantState.java +++ b/src/main/java/project/volunteer/global/common/component/ParticipantState.java @@ -1,6 +1,5 @@ package project.volunteer.global.common.component; -import java.util.Arrays; import java.util.List; import project.volunteer.global.common.converter.CodeCommonType; From de73c917a57bd34a80c9a1308b7822747b10d200 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 20:29:38 +0900 Subject: [PATCH 19/29] =?UTF-8?q?refactor:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20Controller=20DTO=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20API=20document=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 2 +- ...ement.adoc => schedule-participation.adoc} | 40 ++--- ...a => ScheduleParticipationController.java} | 20 ++- ....java => CancellationApprovalRequest.java} | 5 +- ...articipationCompletionApproveRequest.java} | 4 +- .../ScheduleParticipationCommandFacade.java | 4 +- ...ScheduleParticipationControllerAdvice.java | 4 +- .../acceptance/AcceptanceFixtures.java | 8 +- .../acceptance/ScheduleAcceptanceTest.java | 8 +- .../volunteer/document/DocumentTest.java | 85 ++++++++-- .../ScheduleParticipationControllerTest.java | 150 ++++++++++++++++++ .../ScheduleParticipantControllerTest.java | 144 +---------------- 12 files changed, 274 insertions(+), 200 deletions(-) rename src/docs/asciidoc/{schedule-management.adoc => schedule-participation.adoc} (73%) rename src/main/java/project/volunteer/domain/scheduleParticipation/api/{ScheduleParticipantController.java => ScheduleParticipationController.java} (83%) rename src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/{CancelApproval.java => CancellationApprovalRequest.java} (75%) rename src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/{CompleteApproval.java => ParticipationCompletionApproveRequest.java} (73%) create mode 100644 src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index c39da8ca..ac57c59b 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -17,7 +17,7 @@ include::recruitment-participation.adoc[] include::schedule.adoc[] -include::schedule-management.adoc[] +include::schedule-participation.adoc[] include::notice.adoc[] diff --git a/src/docs/asciidoc/schedule-management.adoc b/src/docs/asciidoc/schedule-participation.adoc similarity index 73% rename from src/docs/asciidoc/schedule-management.adoc rename to src/docs/asciidoc/schedule-participation.adoc index 5f1e2094..71ce63a1 100644 --- a/src/docs/asciidoc/schedule-management.adoc +++ b/src/docs/asciidoc/schedule-participation.adoc @@ -1,5 +1,5 @@ -[[Schedlue-Management-API]] -== Schedule Management API +[[Schedlue-Participation-API]] +== Schedule Participation API === 봉사 일정 참여 ==== 발생 가능 예외 @@ -17,17 +17,17 @@ ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/participate-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/participate-schedule/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/participate-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/participate-schedule/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/participate-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/participate-schedule/path-parameters.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/participate-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/participate-schedule/http-response.adoc[] === 봉사 일정 참여 취소 요청 @@ -44,17 +44,17 @@ include::{snippets}/schedule-participant-controller-test/participate-schedule/ht ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/cancel-participation-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/cancel-participation-schedule/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/cancel-participation-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/cancel-participation-schedule/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/cancel-participation-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/cancel-participation-schedule/path-parameters.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/cancel-participation-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/cancel-participation-schedule/http-response.adoc[] === 봉사 일정 취소 요청 승인 @@ -72,20 +72,20 @@ include::{snippets}/schedule-participant-controller-test/cancel-participation-sc ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/approve-cancellation-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-cancellation-schedule/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/approve-cancellation-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-cancellation-schedule/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/approve-cancellation-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-cancellation-schedule/path-parameters.adoc[] *Request Body* -include::{snippets}/schedule-participant-controller-test/approve-cancellation-schedule/request-fields.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-cancellation-schedule/request-fields.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/approve-cancellation-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-cancellation-schedule/http-response.adoc[] === 봉사 일정 참여 완료 승인 @@ -102,20 +102,20 @@ include::{snippets}/schedule-participant-controller-test/approve-cancellation-sc ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/approval-completion-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-participation-completion/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/approval-completion-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-participation-completion/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/approval-completion-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-participation-completion/path-parameters.adoc[] *Request Body* -include::{snippets}/schedule-participant-controller-test/approval-completion-schedule/request-fields.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-participation-completion/request-fields.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/approval-completion-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/approve-participation-completion/http-response.adoc[] === 봉사 일정 참여자 리스트 조회 diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipationController.java similarity index 83% rename from src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipationController.java index 255b7df0..7cbce539 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantController.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipationController.java @@ -8,49 +8,47 @@ import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationQueryFacade; import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; -import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.global.Interceptor.OrganizationAuth; import project.volunteer.global.util.SecurityUtil; import javax.validation.Valid; -import java.util.List; @RestController @RequiredArgsConstructor @RequestMapping("/recruitment") -public class ScheduleParticipantController { +public class ScheduleParticipationController { private final ScheduleParticipationCommandFacade scheduleParticipationCommandFacade; private final ScheduleParticipationQueryFacade scheduleParticipationQueryFacade; @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_TEAM) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/join") - public ResponseEntity scheduleParticipation(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + public ResponseEntity scheduleParticipation(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ scheduleParticipationCommandFacade.participateSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); return ResponseEntity.ok().build(); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_TEAM) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancel") - public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ + public ResponseEntity scheduleCancelRequest(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo){ scheduleParticipationCommandFacade.cancelParticipationSchedule(SecurityUtil.getLoginUserNo(), recruitmentNo, scheduleNo); return ResponseEntity.ok().build(); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/cancelling") - public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, - @RequestBody @Valid CancelApproval dto){ - scheduleParticipationCommandFacade.approvalCancellationSchedule(scheduleNo, dto.getNo()); + public ResponseEntity scheduleCancelApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, + @RequestBody @Valid CancellationApprovalRequest request){ + scheduleParticipationCommandFacade.approvalCancellationSchedule(scheduleNo, request.getScheduleParticipationNos()); return ResponseEntity.ok().build(); } @OrganizationAuth(auth = OrganizationAuth.Auth.ORGANIZATION_ADMIN) @PutMapping("/{recruitmentNo}/schedule/{scheduleNo}/complete") - public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, - @RequestBody @Valid CompleteApproval dto){ + public ResponseEntity scheduleCompleteApproval(@PathVariable Long recruitmentNo, @PathVariable Long scheduleNo, + @RequestBody @Valid ParticipationCompletionApproveRequest request){ - scheduleParticipationCommandFacade.approvalParticipationCompletionSchedule(scheduleNo, dto.getCompletedList()); + scheduleParticipationCommandFacade.approvalParticipationCompletionSchedule(scheduleNo, request.getScheduleParticipationNos()); return ResponseEntity.ok().build(); } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancellationApprovalRequest.java similarity index 75% rename from src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancellationApprovalRequest.java index bc233d33..cd2d0d15 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancelApproval.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CancellationApprovalRequest.java @@ -10,8 +10,7 @@ @Getter @AllArgsConstructor @NoArgsConstructor -public class CancelApproval { - +public class CancellationApprovalRequest { @NotNull - private List no; + private List scheduleParticipationNos; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompleteApproval.java b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipationCompletionApproveRequest.java similarity index 73% rename from src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompleteApproval.java rename to src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipationCompletionApproveRequest.java index 27e55d33..ff0e35a4 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/CompleteApproval.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/api/dto/ParticipationCompletionApproveRequest.java @@ -10,8 +10,8 @@ @Getter @AllArgsConstructor @NoArgsConstructor -public class CompleteApproval { +public class ParticipationCompletionApproveRequest { @NotNull - private List completedList; + private List scheduleParticipationNos; } diff --git a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java index cff28538..819fe82d 100644 --- a/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java +++ b/src/main/java/project/volunteer/domain/scheduleParticipation/service/ScheduleParticipationCommandFacade.java @@ -30,9 +30,9 @@ public void cancelParticipationSchedule(Long userNo, Long recruitmentNo, Long sc scheduleParticipationService.cancelParticipation(schedule, recruitmentParticipation); } - public void approvalCancellationSchedule(Long scheduleNo, List scheduleParticipantNo){ + public void approvalCancellationSchedule(Long scheduleNo, List scheduleParticipantNos){ Schedule schedule = scheduleQueryUsecase.findScheduleInProgress(scheduleNo); - scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNo); + scheduleParticipationService.approvalCancellation(schedule, scheduleParticipantNos); } public void approvalParticipationCompletionSchedule(Long scheduleNo, List scheduleParticipantNos){ diff --git a/src/main/java/project/volunteer/global/error/exhadler/ScheduleParticipationControllerAdvice.java b/src/main/java/project/volunteer/global/error/exhadler/ScheduleParticipationControllerAdvice.java index 1671c0a5..7833e910 100644 --- a/src/main/java/project/volunteer/global/error/exhadler/ScheduleParticipationControllerAdvice.java +++ b/src/main/java/project/volunteer/global/error/exhadler/ScheduleParticipationControllerAdvice.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import project.volunteer.domain.scheduleParticipation.api.ScheduleParticipantController; +import project.volunteer.domain.scheduleParticipation.api.ScheduleParticipationController; import project.volunteer.global.error.exception.BusinessException; import project.volunteer.global.error.exception.ErrorCode; import project.volunteer.global.error.response.BaseErrorResponse; @@ -17,7 +17,7 @@ @Slf4j @RequiredArgsConstructor -@RestControllerAdvice(basePackageClasses = ScheduleParticipantController.class) +@RestControllerAdvice(basePackageClasses = ScheduleParticipationController.class) public class ScheduleParticipationControllerAdvice { private final MessageSource ms; diff --git a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java index e0a5821c..ee15cb17 100644 --- a/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java +++ b/src/test/java/project/volunteer/acceptance/AcceptanceFixtures.java @@ -18,8 +18,8 @@ import project.volunteer.domain.recruitment.domain.repeatPeriod.Day; import project.volunteer.domain.recruitment.domain.repeatPeriod.Period; import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; -import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; -import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; +import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; @@ -147,7 +147,7 @@ public class AcceptanceFixtures { } public static ExtractableResponse 봉사_일정_참여_취소승인(String token, Long recruitmentNo, Long scheduleNo, - CancelApproval request) { + CancellationApprovalRequest request) { return given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header(AUTHORIZATION_HEADER, token) @@ -183,7 +183,7 @@ public class AcceptanceFixtures { } public static ExtractableResponse 봉사_일정_참여완료_승인(String token, Long recruitmentNo, Long scheduleNo, - CompleteApproval request) { + ParticipationCompletionApproveRequest request) { return given().log().all() .contentType(MediaType.APPLICATION_JSON_VALUE) .header(AUTHORIZATION_HEADER, token) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java index e0a74648..46cf9ff1 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleAcceptanceTest.java @@ -30,8 +30,8 @@ import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitment.domain.repeatPeriod.Period; import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; -import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; -import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; +import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; @@ -661,7 +661,7 @@ void findScheduleCompleteApprovedState() { final List completedScheduleParticipants = 봉사_일정_참여완료_조회(bonsikToken, recruitmentNo, scheduleNo2); - final CompleteApproval completeApprovalRequest = new CompleteApproval( + final ParticipationCompletionApproveRequest completeApprovalRequest = new ParticipationCompletionApproveRequest( completedScheduleParticipants.stream() .map(CompletedParticipantDetail::getScheduleParticipationNo) .collect(Collectors.toList()) @@ -763,7 +763,7 @@ void findScheduleAvailableStateAfterApprovalCancel() { final List cancelledParticipants = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, scheduleNo2); - final CancelApproval cancelApprovalRequest = new CancelApproval( + final CancellationApprovalRequest cancelApprovalRequest = new CancellationApprovalRequest( cancelledParticipants.stream() .map(CancelledParticipantDetail::getScheduleParticipationNo) .collect(Collectors.toList())); diff --git a/src/test/java/project/volunteer/document/DocumentTest.java b/src/test/java/project/volunteer/document/DocumentTest.java index 2d661bc9..1e8c5d1e 100644 --- a/src/test/java/project/volunteer/document/DocumentTest.java +++ b/src/test/java/project/volunteer/document/DocumentTest.java @@ -32,6 +32,8 @@ import project.volunteer.domain.recruitment.domain.VolunteeringCategory; import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitment.repository.RepeatPeriodRepository; +import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; +import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.sehedule.domain.Schedule; import project.volunteer.domain.sehedule.repository.ScheduleRepository; import project.volunteer.domain.user.dao.UserRepository; @@ -82,7 +84,10 @@ public abstract class DocumentTest { protected ScheduleRepository scheduleRepository; @Autowired - protected RecruitmentParticipationRepository participantRepository; + protected RecruitmentParticipationRepository recruitmentParticipationRepository; + + @Autowired + protected ScheduleParticipationRepository scheduleParticipationRepository; @Autowired protected ImageRepository imageRepository; @@ -93,20 +98,32 @@ public abstract class DocumentTest { protected String recruitmentTeamAccessToken1; protected String recruitmentTeamAccessToken2; protected String recruitmentTeamAccessToken3; + protected String recruitmentTeamAccessToken4; + protected String recruitmentTeamAccessToken5; + protected String recruitmentTeamAccessToken6; protected String loginUserAccessToken; protected User ownerUser; protected User teamUser1; protected User teamUser2; protected User teamUser3; - protected User user; + protected User teamUser4; + protected User teamUser5; + protected User teamUser6; + protected User basicUser; protected Recruitment recruitment1; protected Recruitment recruitment2; protected RecruitmentParticipation recruitmentParticipation1; protected RecruitmentParticipation recruitmentParticipation2; protected RecruitmentParticipation recruitmentParticipation3; + protected RecruitmentParticipation recruitmentParticipation4; + protected RecruitmentParticipation recruitmentParticipation5; + protected RecruitmentParticipation recruitmentParticipation6; protected Schedule schedule1; protected Schedule schedule2; protected Schedule schedule3; + protected ScheduleParticipation scheduleParticipation4; + protected ScheduleParticipation scheduleParticipation5; + protected ScheduleParticipation scheduleParticipation6; @BeforeEach @@ -116,17 +133,23 @@ void setUp() { recruitmentTeamAccessToken1 = jwtProvider.createAccessToken(teamUser1.getId()); recruitmentTeamAccessToken2 = jwtProvider.createAccessToken(teamUser2.getId()); recruitmentTeamAccessToken3 = jwtProvider.createAccessToken(teamUser3.getId()); - loginUserAccessToken = jwtProvider.createAccessToken(user.getId()); + recruitmentTeamAccessToken4 = jwtProvider.createAccessToken(teamUser4.getId()); + recruitmentTeamAccessToken5 = jwtProvider.createAccessToken(teamUser5.getId()); + recruitmentTeamAccessToken6 = jwtProvider.createAccessToken(teamUser6.getId()); + + loginUserAccessToken = jwtProvider.createAccessToken(basicUser.getId()); } private void saveBaseData() { + // 봉사 모집글 정보 저장 ownerUser = userRepository.save( new User("bonsik1234", "password", "bonsik", "test@email.com", Gender.M, LocalDate.of(1999, 7, 27), "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); recruitment1 = recruitmentRepository.save( - new Recruitment("title1", "content", VolunteeringCategory.ADMINSTRATION_ASSISTANCE, VolunteeringType.REG, - VolunteerType.TEENAGER, 9999,1,true, "unicef", + new Recruitment("title1", "content", VolunteeringCategory.ADMINSTRATION_ASSISTANCE, + VolunteeringType.REG, + VolunteerType.TEENAGER, 9999, 1, true, "unicef", new Address("11", "1111", "test", "test"), new Coordinate(1.2F, 2.2F), new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 3, 3), HourFormat.AM, @@ -141,7 +164,7 @@ private void saveBaseData() { recruitment2 = recruitmentRepository.save( new Recruitment("2title2", "content", VolunteeringCategory.CULTURAL_EVENT, VolunteeringType.REG, - VolunteerType.TEENAGER, 9999,0,true, "unicef", + VolunteerType.TEENAGER, 9999, 0, true, "unicef", new Address("11", "1111", "test", "test"), new Coordinate(1.2F, 2.2F), new Timetable(LocalDate.of(2024, 2, 10), LocalDate.of(2024, 4, 3), HourFormat.AM, @@ -154,6 +177,7 @@ private void saveBaseData() { image2.setStorage(storage2); imageRepository.save(image2); + // 일정 정보 저장 schedule1 = scheduleRepository.save( new Schedule(new Timetable(LocalDate.of(2024, 2, 10), LocalDate.of(2024, 2, 10), HourFormat.AM, LocalTime.now(), 10), @@ -178,25 +202,64 @@ private void saveBaseData() { 100, IsDeleted.N, 0, recruitment1) ); + // 봉사 모집글 참여 유저 저장 teamUser1 = userRepository.save( new User("soeun1234", "password", "soeun", "test@email.com", Gender.M, LocalDate.of(2001, 6, 27), "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); - recruitmentParticipation1 = participantRepository.save(new RecruitmentParticipation(recruitment1, teamUser1, ParticipantState.JOIN_APPROVAL)); + recruitmentParticipation1 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser1, ParticipantState.JOIN_APPROVAL)); + recruitment1.increaseParticipationNum(1); teamUser2 = userRepository.save( new User("chang1234", "password", "chang", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); - recruitmentParticipation2 = participantRepository.save(new RecruitmentParticipation(recruitment1, teamUser2, ParticipantState.JOIN_REQUEST)); + recruitmentParticipation2 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser2, ParticipantState.JOIN_REQUEST)); teamUser3 = userRepository.save( new User("mong1234", "password", "mong", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); - recruitmentParticipation3 = participantRepository.save(new RecruitmentParticipation(recruitment1, teamUser3, ParticipantState.JOIN_REQUEST)); + recruitmentParticipation3 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser3, ParticipantState.JOIN_REQUEST)); - user = userRepository.save( - new User("user1234", "password", "user", "test@email.com", Gender.M, LocalDate.of(2001, 6, 27), + teamUser4 = userRepository.save( + new User("sik1234", "password", "sik", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); + recruitmentParticipation4 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser4, ParticipantState.JOIN_APPROVAL)); + recruitment1.increaseParticipationNum(1); + teamUser5 = userRepository.save( + new User("kkk1234", "password", "kkk", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), + "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); + recruitmentParticipation5 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser5, ParticipantState.JOIN_APPROVAL)); + recruitment1.increaseParticipationNum(1); + + teamUser6 = userRepository.save( + new User("zibra1234", "password", "zibra", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), + "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); + recruitmentParticipation6 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser6, ParticipantState.JOIN_APPROVAL)); + recruitment1.increaseParticipationNum(1); + + // 봉사 일정 참여 유저 정보 저장 + scheduleParticipation4 = scheduleParticipationRepository.save( + new ScheduleParticipation(schedule1, recruitmentParticipation4, ParticipantState.PARTICIPATING)); + schedule1.increaseParticipationNum(1); + + scheduleParticipation5 = scheduleParticipationRepository.save( + new ScheduleParticipation(schedule1, recruitmentParticipation5, ParticipantState.PARTICIPATION_CANCEL)); + schedule1.increaseParticipationNum(1); + + scheduleParticipation6 = scheduleParticipationRepository.save( + new ScheduleParticipation(schedule1, recruitmentParticipation6, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)); + schedule1.increaseParticipationNum(1); + + // 일반 로그인 유저 정보 저장 + basicUser = userRepository.save( + new User("user1234", "password", "user", "test@email.com", Gender.M, LocalDate.of(2001, 6, 27), + "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); } protected String toJson(T data) throws JsonProcessingException { diff --git a/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java b/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java new file mode 100644 index 00000000..552491ef --- /dev/null +++ b/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java @@ -0,0 +1,150 @@ +package project.volunteer.document; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; + +public class ScheduleParticipationControllerTest extends DocumentTest { + + @DisplayName("일정 참여에 성공하다.") + @Test + void participateSchedule() throws Exception { + //given & when + given(clock.instant()).willReturn(Instant.parse("2024-02-09T10:00:00Z")); + + ResultActions result = mockMvc.perform( + put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .header(AUTHORIZATION_HEADER, recruitmentTeamAccessToken1)); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ) + ) + ); + } + + @DisplayName("일정 참여 취소 요청에 성공하다.") + @Test + void cancelParticipationSchedule() throws Exception { + //given & when + given(clock.instant()).willReturn(Instant.parse("2024-02-09T10:00:00Z")); + + ResultActions result = mockMvc.perform( + put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancel", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .header(AUTHORIZATION_HEADER, recruitmentTeamAccessToken4)); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ) + ) + ); + } + + @DisplayName("일정 참여 취소 요청 승인에 성공하다.") + @Test + void approveCancellationSchedule() throws Exception { + //given & when + given(clock.instant()).willReturn(Instant.parse("2024-02-09T10:00:00Z")); + + final CancellationApprovalRequest request = new CancellationApprovalRequest( + List.of(scheduleParticipation5.getId())); + + ResultActions result = mockMvc.perform( + put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER, recruitmentOwnerAccessToken) + .content(toJson(request))); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ), + requestFields( + fieldWithPath("scheduleParticipationNos").type(JsonFieldType.ARRAY) + .description("일정 참여자 고유키 PK") + ) + ) + ); + } + + @DisplayName("일정 참여 완료 승인에 성공하다.") + @Test + void approveParticipationCompletion() throws Exception { + //given & when + final ParticipationCompletionApproveRequest request = new ParticipationCompletionApproveRequest( + List.of(scheduleParticipation6.getId())); + + ResultActions result = mockMvc.perform( + put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/complete", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER, recruitmentOwnerAccessToken) + .content(toJson(request))); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ), + requestFields( + fieldWithPath("scheduleParticipationNos").type(JsonFieldType.ARRAY) + .description("일정 참여자 고유키 PK") + ) + ) + ); + } + +} diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java index 613ace0c..488576a1 100644 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java +++ b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java @@ -30,8 +30,8 @@ import project.volunteer.domain.recruitment.domain.VolunteerType; import project.volunteer.domain.recruitment.domain.VolunteeringCategory; import project.volunteer.domain.recruitment.domain.VolunteeringType; -import project.volunteer.domain.scheduleParticipation.api.dto.CancelApproval; -import project.volunteer.domain.scheduleParticipation.api.dto.CompleteApproval; +import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; @@ -134,36 +134,6 @@ public void deleteS3Image() { //S3에 테스트를 위해 저장한 이미지 } } - @Test - @DisplayName("일정 참가 신청에 성공하다.") - @Transactional - @WithUserDetails(value = "spct_test", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void participateSchedule() throws Exception { - //given - 봉사모집글_팀원_등록(saveRecruitment, loginUser); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", - saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .header(AUTHORIZATION_HEADER, "access Token") - ); - - //then - result.andExpect(status().isOk()) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ) - ) - ); - } - @Disabled @Test @DisplayName("팀원이 아닌 사용자가 일정 참가 신청을 시도하다.") @@ -176,74 +146,6 @@ public void schedule_participate_forbidden() throws Exception { .andDo(print()); } - @Test - @DisplayName("일정 참가 취소 요청에 성공하다.") - @Transactional - @WithUserDetails(value = "spct_test", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void cancelParticipationSchedule() throws Exception { - //given - RecruitmentParticipation participant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); - 일정_참여상태_추가(saveSchedule, participant, ParticipantState.PARTICIPATING); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancel", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .header(AUTHORIZATION_HEADER, "access Token") - ); - - //then - result.andExpect(status().isOk()) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ) - ) - ); - } - - - @Test - @DisplayName("일정 참가 취소 요청 승인에 성공하다.") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void approveCancellationSchedule() throws Exception { - //given - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); - ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(List.of(newSp.getId())); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", - saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .contentType(MediaType.APPLICATION_JSON) - .header(AUTHORIZATION_HEADER, "access Token") - .content(toJson(dto)) - ); - - //then - result.andExpect(status().isOk()) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ), - requestFields( - fieldWithPath("no").type(JsonFieldType.NUMBER).description("취소 요청자 고유키 PK") - ) - ) - ); - } - @Disabled @Test @DisplayName("방장이 아닌 사용자가 일정 참가 취소 요청 승인을 시도하다.") @@ -253,7 +155,7 @@ public void cancelApprove_forbidden() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(List.of(newSp.getId())); + CancellationApprovalRequest dto = new CancellationApprovalRequest(List.of(newSp.getId())); //when & then mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) @@ -272,7 +174,7 @@ public void cancelApprove_notValid() throws Exception { //given RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancelApproval dto = new CancelApproval(null); //필수 파라미터 누락 + CancellationApprovalRequest dto = new CancellationApprovalRequest(null); //필수 파라미터 누락 //when & then mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) @@ -282,44 +184,6 @@ public void cancelApprove_notValid() throws Exception { .andDo(print()); } - @Test - @DisplayName("일정 참가 완료 승인에 성공하다.") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void approvalCompletionSchedule() throws Exception { - //given - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); - ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - CompleteApproval dto = new CompleteApproval(List.of(newSp.getId())); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/complete", - saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .contentType(MediaType.APPLICATION_JSON) - .header(AUTHORIZATION_HEADER, "access Token") - .content(toJson(dto)) - ); - - //then - result.andExpect(status().isOk()) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ), - requestFields( - fieldWithPath("completedList").type(JsonFieldType.ARRAY).description("참가 완료자 고유키 PK") - ) - ) - ); - } - - @Test @DisplayName("일정 참가자 리스트 조회에 성공하다.") @Transactional From c0d2c195252d8d4e46c074fef0175799fac67208 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 20:51:27 +0900 Subject: [PATCH 20/29] =?UTF-8?q?test:=20=EC=9D=BC=EC=A0=95=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EC=A1=B0=ED=9A=8C=20API=20document=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/schedule-participation.adoc | 30 +- .../volunteer/document/DocumentTest.java | 14 + .../ScheduleParticipationControllerTest.java | 107 +++++ .../ScheduleParticipantControllerTest.java | 370 ------------------ 4 files changed, 136 insertions(+), 385 deletions(-) delete mode 100644 src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java diff --git a/src/docs/asciidoc/schedule-participation.adoc b/src/docs/asciidoc/schedule-participation.adoc index 71ce63a1..34607af7 100644 --- a/src/docs/asciidoc/schedule-participation.adoc +++ b/src/docs/asciidoc/schedule-participation.adoc @@ -130,20 +130,20 @@ include::{snippets}/schedule-participation-controller-test/approve-participation ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/find-list-participant-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/find-active-participants/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/find-list-participant-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/find-active-participants/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/find-list-participant-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/find-active-participants/path-parameters.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/find-list-participant-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/find-active-participants/http-response.adoc[] *Response Body* -include::{snippets}/schedule-participant-controller-test/find-list-participant-schedule/response-fields.adoc[] +include::{snippets}/schedule-participation-controller-test/find-active-participants/response-fields.adoc[] === 봉사 일정 참가 취소자 리스트 조회 @@ -158,20 +158,20 @@ include::{snippets}/schedule-participant-controller-test/find-list-participant-s ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/find-list-cancellation-requester-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/find-cancelled-participants/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/find-list-cancellation-requester-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/find-cancelled-participants/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/find-list-cancellation-requester-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/find-cancelled-participants/path-parameters.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/find-list-cancellation-requester-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/find-cancelled-participants/http-response.adoc[] *Response Body* -include::{snippets}/schedule-participant-controller-test/find-list-cancellation-requester-schedule/response-fields.adoc[] +include::{snippets}/schedule-participation-controller-test/find-cancelled-participants/response-fields.adoc[] === 봉사 일정 참가 완료자 리스트 조회 @@ -186,17 +186,17 @@ include::{snippets}/schedule-participant-controller-test/find-list-cancellation- ==== 요청 *HTTP Request* -include::{snippets}/schedule-participant-controller-test/find-list-participant-completed-schedule/http-request.adoc[] +include::{snippets}/schedule-participation-controller-test/find-completed-participants/http-request.adoc[] *Request Header* -include::{snippets}/schedule-participant-controller-test/find-list-participant-completed-schedule/request-headers.adoc[] +include::{snippets}/schedule-participation-controller-test/find-completed-participants/request-headers.adoc[] *Request Path* -include::{snippets}/schedule-participant-controller-test/find-list-participant-completed-schedule/path-parameters.adoc[] +include::{snippets}/schedule-participation-controller-test/find-completed-participants/path-parameters.adoc[] ==== 응답 *HTTP Response* -include::{snippets}/schedule-participant-controller-test/find-list-participant-completed-schedule/http-response.adoc[] +include::{snippets}/schedule-participation-controller-test/find-completed-participants/http-response.adoc[] *Response Body* -include::{snippets}/schedule-participant-controller-test/find-list-participant-completed-schedule/response-fields.adoc[] +include::{snippets}/schedule-participation-controller-test/find-completed-participants/response-fields.adoc[] diff --git a/src/test/java/project/volunteer/document/DocumentTest.java b/src/test/java/project/volunteer/document/DocumentTest.java index 1e8c5d1e..837934b9 100644 --- a/src/test/java/project/volunteer/document/DocumentTest.java +++ b/src/test/java/project/volunteer/document/DocumentTest.java @@ -109,6 +109,7 @@ public abstract class DocumentTest { protected User teamUser4; protected User teamUser5; protected User teamUser6; + protected User teamUser7; protected User basicUser; protected Recruitment recruitment1; protected Recruitment recruitment2; @@ -118,12 +119,14 @@ public abstract class DocumentTest { protected RecruitmentParticipation recruitmentParticipation4; protected RecruitmentParticipation recruitmentParticipation5; protected RecruitmentParticipation recruitmentParticipation6; + protected RecruitmentParticipation recruitmentParticipation7; protected Schedule schedule1; protected Schedule schedule2; protected Schedule schedule3; protected ScheduleParticipation scheduleParticipation4; protected ScheduleParticipation scheduleParticipation5; protected ScheduleParticipation scheduleParticipation6; + protected ScheduleParticipation scheduleParticipation7; @BeforeEach @@ -243,6 +246,13 @@ private void saveBaseData() { new RecruitmentParticipation(recruitment1, teamUser6, ParticipantState.JOIN_APPROVAL)); recruitment1.increaseParticipationNum(1); + teamUser7 = userRepository.save( + new User("umm1234", "password", "umm", "test@email.com", Gender.M, LocalDate.of(2005, 8, 27), + "http://www...", true, true, true, Role.USER, "kakao", "kakao1234", null)); + recruitmentParticipation7 = recruitmentParticipationRepository.save( + new RecruitmentParticipation(recruitment1, teamUser7, ParticipantState.JOIN_APPROVAL)); + recruitment1.increaseParticipationNum(1); + // 봉사 일정 참여 유저 정보 저장 scheduleParticipation4 = scheduleParticipationRepository.save( new ScheduleParticipation(schedule1, recruitmentParticipation4, ParticipantState.PARTICIPATING)); @@ -256,6 +266,10 @@ private void saveBaseData() { new ScheduleParticipation(schedule1, recruitmentParticipation6, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED)); schedule1.increaseParticipationNum(1); + scheduleParticipation7 = scheduleParticipationRepository.save( + new ScheduleParticipation(schedule1, recruitmentParticipation7, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL)); + schedule1.increaseParticipationNum(1); + // 일반 로그인 유저 정보 저장 basicUser = userRepository.save( new User("user1234", "password", "user", "test@email.com", Gender.M, LocalDate.of(2001, 6, 27), diff --git a/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java b/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java index 552491ef..7e02cbe0 100644 --- a/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java +++ b/src/test/java/project/volunteer/document/ScheduleParticipationControllerTest.java @@ -3,9 +3,11 @@ import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -147,4 +149,109 @@ void approveParticipationCompletion() throws Exception { ); } + @DisplayName("일정 참여자 리스트 조회에 성공하다.") + @Test + void findActiveParticipants() throws Exception { + //given & when + ResultActions result = mockMvc.perform( + get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/participating", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .header(AUTHORIZATION_HEADER, recruitmentOwnerAccessToken)); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ), + responseFields( + fieldWithPath("participating[].nickname").type(JsonFieldType.STRING) + .description("참여자 닉네임"), + fieldWithPath("participating[].email").type(JsonFieldType.STRING) + .description("참여자 이메일"), + fieldWithPath("participating[].profile").type(JsonFieldType.STRING) + .description("참여자 프로필 URL") + ) + ) + ); + } + + @DisplayName("일정 취소자 리스트 조회에 성공하다.") + @Test + void findCancelledParticipants() throws Exception { + //given when + ResultActions result = mockMvc.perform( + get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .header(AUTHORIZATION_HEADER, recruitmentOwnerAccessToken)); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ), + responseFields( + fieldWithPath("cancelling[].scheduleParticipationNo").type(JsonFieldType.NUMBER) + .description("일정 참여자 고유키 PK"), + fieldWithPath("cancelling[].nickname").type(JsonFieldType.STRING) + .description("참여자 닉네임"), + fieldWithPath("cancelling[].email").type(JsonFieldType.STRING) + .description("참여자 이메일"), + fieldWithPath("cancelling[].profile").type(JsonFieldType.STRING) + .description("참여자 프로필 URL") + ) + ) + ); + } + + @DisplayName("일정 참여 완료자 리스트 조회에 성공하다.") + @Test + void findCompletedParticipants() throws Exception { + //given & when + ResultActions result = mockMvc.perform( + get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/completion", recruitment1.getRecruitmentNo(), + schedule1.getScheduleNo()) + .header(AUTHORIZATION_HEADER, recruitmentOwnerAccessToken)); + + //then + result.andExpect(status().isOk()) + .andDo(print()) + .andDo( + restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") + ), + pathParameters( + parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), + parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") + ), + responseFields( + fieldWithPath("done[].scheduleParticipationNo").type(JsonFieldType.NUMBER) + .description("일정 참여자 고유키 PK"), + fieldWithPath("done[].nickname").type(JsonFieldType.STRING) + .description("참여자 닉네임"), + fieldWithPath("done[].email").type(JsonFieldType.STRING) + .description("참여자 이메일"), + fieldWithPath("done[].profile").type(JsonFieldType.STRING) + .description("참여자 URL"), + fieldWithPath("done[].isApproved").type(JsonFieldType.BOOLEAN) + .description("참여 완료 승인 여부") + ) + ) + ); + } + } diff --git a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java b/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java deleted file mode 100644 index 488576a1..00000000 --- a/src/test/java/project/volunteer/domain/scheduleParticipation/api/ScheduleParticipantControllerTest.java +++ /dev/null @@ -1,370 +0,0 @@ -package project.volunteer.domain.scheduleParticipation.api; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; -import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.security.test.context.support.TestExecutionEvent; -import org.springframework.security.test.context.support.WithUserDetails; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.transaction.annotation.Transactional; -import project.volunteer.domain.image.application.ImageService; -import project.volunteer.domain.image.application.dto.ImageParam; -import project.volunteer.domain.image.dao.ImageRepository; -import project.volunteer.domain.image.domain.Image; -import project.volunteer.domain.recruitment.repository.RecruitmentRepository; -import project.volunteer.domain.recruitmentParticipation.domain.RecruitmentParticipation; -import project.volunteer.global.common.component.RealWorkCode; -import project.volunteer.domain.recruitmentParticipation.repository.RecruitmentParticipationRepository; -import project.volunteer.domain.recruitment.domain.Recruitment; -import project.volunteer.domain.recruitment.domain.VolunteerType; -import project.volunteer.domain.recruitment.domain.VolunteeringCategory; -import project.volunteer.domain.recruitment.domain.VolunteeringType; -import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; -import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; -import project.volunteer.domain.scheduleParticipation.repository.ScheduleParticipationRepository; -import project.volunteer.domain.scheduleParticipation.domain.ScheduleParticipation; -import project.volunteer.domain.scheduleParticipation.service.ScheduleParticipationCommandUseCase; -import project.volunteer.domain.sehedule.repository.ScheduleRepository; -import project.volunteer.domain.sehedule.domain.Schedule; -import project.volunteer.domain.image.domain.Storage; -import project.volunteer.domain.user.dao.UserRepository; -import project.volunteer.domain.user.domain.Gender; -import project.volunteer.domain.user.domain.Role; -import project.volunteer.domain.user.domain.User; -import project.volunteer.global.common.component.*; -import project.volunteer.global.infra.s3.FileService; -import project.volunteer.global.test.WithMockCustomUser; -import project.volunteer.document.restdocs.config.RestDocsConfiguration; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import java.io.FileInputStream; -import java.io.IOException; -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.List; - -import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -@AutoConfigureRestDocs -@Import(RestDocsConfiguration.class) -class ScheduleParticipantControllerTest { - - @Autowired MockMvc mockMvc; - @PersistenceContext EntityManager em; - @Autowired UserRepository userRepository; - @Autowired RecruitmentRepository recruitmentRepository; - @Autowired ScheduleRepository scheduleRepository; - @Autowired - RecruitmentParticipationRepository participantRepository; - @Autowired ScheduleParticipationRepository scheduleParticipationRepository; - @Autowired - ScheduleParticipationCommandUseCase spService; - @Autowired ImageService imageService; - @Autowired ImageRepository imageRepository; - @Autowired FileService fileService; - @Autowired ObjectMapper objectMapper; - @Autowired RestDocumentationResultHandler restDocs; - - final String AUTHORIZATION_HEADER = "accessToken"; - private User writer; - private User loginUser; - private Recruitment saveRecruitment; - private Schedule saveSchedule; - private List deleteImageNo = new ArrayList<>(); - @BeforeEach - void init(){ - //작성자 저장 - User writerUser = User.createUser("spct1234", "spct1234", "spct1234", "spct1234", Gender.M, LocalDate.now(), "picture", - true, true, true, Role.USER, "kakao", "spct1234", null); - writer = userRepository.save(writerUser); - - //모집글 저장 - Recruitment createRecruitment = new Recruitment("title", "content", VolunteeringCategory.EDUCATION, VolunteeringType.REG, - VolunteerType.ADULT, 9999,0,true, "unicef", - new Address("111", "11", "test", "test"), - new Coordinate(1.2F, 2.2F), - new Timetable(LocalDate.of(2024, 1, 10), LocalDate.of(2024, 3, 3), HourFormat.AM, - LocalTime.now(), 10), - 0, 0, true, IsDeleted.N, writerUser); - saveRecruitment = recruitmentRepository.save(createRecruitment); - - //일정 저장 - Schedule createSchedule = Schedule.create( - saveRecruitment, - new Timetable( - LocalDate.now().plusMonths(1), LocalDate.now().plusMonths(1), - HourFormat.AM, LocalTime.now(), 3), - "content", "organization", - Address.createAddress("11", "1111", "details", "fullName"), 3); - saveSchedule = scheduleRepository.save(createSchedule); - - //로그인 유저 저장 - User login = User.createUser("spct_test", "spct_test", "spct_test", "spct_test", Gender.M, LocalDate.now(), "picture", - true, true, true, Role.USER, "kakao", "spct_test", null); - loginUser = userRepository.save(login); - } - @AfterEach - public void deleteS3Image() { //S3에 테스트를 위해 저장한 이미지 삭제 - for(Long id : deleteImageNo){ - Image image = imageRepository.findById(id).get(); - Storage storage = image.getStorage(); - fileService.deleteFile(storage.getFakeImageName()); - } - } - - @Disabled - @Test - @DisplayName("팀원이 아닌 사용자가 일정 참가 신청을 시도하다.") - @Transactional - @WithMockCustomUser(tempValue = "spct_forbidden") - public void schedule_participate_forbidden() throws Exception { - - mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo())) - .andExpect(status().isForbidden()) - .andDo(print()); - } - - @Disabled - @Test - @DisplayName("방장이 아닌 사용자가 일정 참가 취소 요청 승인을 시도하다.") - @Transactional - @WithUserDetails(value = "spct_test", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void cancelApprove_forbidden() throws Exception { - //given - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); - ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancellationApprovalRequest dto = new CancellationApprovalRequest(List.of(newSp.getId())); - - //when & then - mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .contentType(MediaType.APPLICATION_JSON) - .content(toJson(dto))) - .andExpect(status().isForbidden()) - .andDo(print()); - } - - @Disabled - @Test - @DisplayName("일정 참가 취소 요청 승인시 필수 파라미터를 누락하다.") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void cancelApprove_notValid() throws Exception { - //given - RecruitmentParticipation newParticipant = 봉사모집글_팀원_등록(saveRecruitment, loginUser); - ScheduleParticipation newSp = 일정_참여상태_추가(saveSchedule, newParticipant, ParticipantState.PARTICIPATION_CANCEL); - CancellationApprovalRequest dto = new CancellationApprovalRequest(null); //필수 파라미터 누락 - - //when & then - mockMvc.perform(put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .contentType(MediaType.APPLICATION_JSON) - .content(toJson(dto))) - .andExpect(status().isBadRequest()) - .andDo(print()); - } - - @Test - @DisplayName("일정 참가자 리스트 조회에 성공하다.") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void findListParticipantSchedule() throws Exception { - //given - User test1 = 사용자_등록("test1", "test1", "test1@naver.com"); - RecruitmentParticipation participant1 = 봉사모집글_팀원_등록(saveRecruitment, test1); - 일정_참여상태_추가(saveSchedule, participant1, ParticipantState.PARTICIPATING); - - User test2 = 사용자_등록("test2", "test2", "test2@naver.com"); - RecruitmentParticipation participant2 = 봉사모집글_팀원_등록(saveRecruitment, test2); - 일정_참여상태_추가(saveSchedule, participant2, ParticipantState.PARTICIPATING); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/participating", - saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .header(AUTHORIZATION_HEADER, "access Token") - ); - - //then - result.andExpect(status().isOk()) - .andExpect(jsonPath("$.participating[0].nickname").value(test1.getNickName())) - .andExpect(jsonPath("$.participating[0].email").value(test1.getEmail())) - .andExpect(jsonPath("$.participating[0].profile").value(test1.getPicture())) - .andExpect(jsonPath("$.participating[1].nickname").value(test2.getNickName())) - .andExpect(jsonPath("$.participating[1].email").value(test2.getEmail())) - .andExpect(jsonPath("$.participating[1].profile").value(test2.getPicture())) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ), - responseFields( - fieldWithPath("participating[].nickname").type(JsonFieldType.STRING).description("참여자 닉네임"), - fieldWithPath("participating[].email").type(JsonFieldType.STRING).description("참여자 이메일"), - fieldWithPath("participating[].profile").type(JsonFieldType.STRING).description("참여자 프로필 URL") - ) - ) - ); - } - - @Test - @DisplayName("일정 참여 취소 요청자 리스트 조회에 성공하다.") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void findListCancellationRequesterSchedule() throws Exception { - //given - User test1 = 사용자_등록("test1", "test1", "test1@naver.com"); - RecruitmentParticipation participant1 = 봉사모집글_팀원_등록(saveRecruitment, test1); - ScheduleParticipation scheduleParticipation1 = 일정_참여상태_추가(saveSchedule, participant1, - ParticipantState.PARTICIPATION_CANCEL); - - User test2 = 사용자_등록("test2", "test2", "test2@naver.com"); - RecruitmentParticipation participant2 = 봉사모집글_팀원_등록(saveRecruitment, test2); - ScheduleParticipation scheduleParticipation2 = - 일정_참여상태_추가(saveSchedule, participant2, ParticipantState.PARTICIPATION_CANCEL); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", - saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .header(AUTHORIZATION_HEADER, "access Token") - ); - - //then - result.andExpect(status().isOk()) - .andExpect(jsonPath("$.cancelling[0].scheduleParticipationNo").value(scheduleParticipation1.getId())) - .andExpect(jsonPath("$.cancelling[0].nickname").value(test1.getNickName())) - .andExpect(jsonPath("$.cancelling[0].email").value(test1.getEmail())) - .andExpect(jsonPath("$.cancelling[0].profile").value(test1.getPicture())) - .andExpect(jsonPath("$.cancelling[1].scheduleParticipationNo").value(scheduleParticipation2.getId())) - .andExpect(jsonPath("$.cancelling[1].nickname").value(test2.getNickName())) - .andExpect(jsonPath("$.cancelling[1].email").value(test2.getEmail())) - .andExpect(jsonPath("$.cancelling[1].profile").value(test2.getPicture())) - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ), - responseFields( - fieldWithPath("cancelling[].scheduleParticipationNo").type(JsonFieldType.NUMBER).description("취소 요청자 고유키 PK"), - fieldWithPath("cancelling[].nickname").type(JsonFieldType.STRING).description("취소 요청자 닉네임"), - fieldWithPath("cancelling[].email").type(JsonFieldType.STRING).description("취소 요청자 이메일"), - fieldWithPath("cancelling[].profile").type(JsonFieldType.STRING).description("취소 요청자 프로필 URL") - ) - ) - ); - } - - @Test - @DisplayName("일정 참가 완료자 리스트 조회") - @Transactional - @WithUserDetails(value = "spct1234", setupBefore = TestExecutionEvent.TEST_EXECUTION) - public void findListParticipantCompletedSchedule() throws Exception { - //given - User test1 = 사용자_등록("test1", "test1", "test1@naver.com"); - RecruitmentParticipation participant1 = 봉사모집글_팀원_등록(saveRecruitment, test1); - 일정_참여상태_추가(saveSchedule, participant1, ParticipantState.PARTICIPATION_COMPLETE_APPROVAL); - - User test2 = 사용자_등록("test2", "test2", "test2@naver.com"); - Image uploadImage = 업로드_이미지_등록(test2.getUserNo(), RealWorkCode.USER); - RecruitmentParticipation participant2 = 봉사모집글_팀원_등록(saveRecruitment, test2); - 일정_참여상태_추가(saveSchedule, participant2, ParticipantState.PARTICIPATION_COMPLETE_UNAPPROVED); - - //when - ResultActions result = mockMvc.perform(RestDocumentationRequestBuilders.get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/completion", saveRecruitment.getRecruitmentNo(), saveSchedule.getScheduleNo()) - .header(AUTHORIZATION_HEADER, "access Token") - ); - - //then - result.andExpect(status().isOk()) - /*.andExpect(jsonPath("$.done[0].no").value(test1.getUserNo())) - .andExpect(jsonPath("$.done[0].nickname").value(test1.getNickName())) - .andExpect(jsonPath("$.done[0].email").value(test1.getEmail())) - .andExpect(jsonPath("$.done[0].profile").value(test1.getPicture())) - .andExpect(jsonPath("$.done[0].status").value(StateResponse.COMPLETE_APPROVED.getId())) - .andExpect(jsonPath("$.done[1].no").value(test2.getUserNo())) - .andExpect(jsonPath("$.done[1].nickname").value(test2.getNickName())) - .andExpect(jsonPath("$.done[1].email").value(test2.getEmail())) - .andExpect(jsonPath("$.done[1].profile").value(uploadImage.getStorage().getImagePath())) - .andExpect(jsonPath("$.done[1].status").value(StateResponse.COMPLETE_UNAPPROVED.getId()))*/ - .andDo(print()) - .andDo( - restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION_HEADER).description("JWT Access Token") - ), - pathParameters( - parameterWithName("recruitmentNo").description("봉사 모집글 고유키 PK"), - parameterWithName("scheduleNo").description("봉사 일정 고유키 PK") - ), - responseFields( - fieldWithPath("done[].scheduleParticipationNo").type(JsonFieldType.NUMBER).description("참가 완료자 고유키 PK"), - fieldWithPath("done[].nickname").type(JsonFieldType.STRING).description("참가 완료자 닉네임"), - fieldWithPath("done[].email").type(JsonFieldType.STRING).description("참가 완료자 이메일"), - fieldWithPath("done[].profile").type(JsonFieldType.STRING).description("참가 완료자 프로필 URL"), - fieldWithPath("done[].status").type(JsonFieldType.STRING).description("Code ClientState 참고바람.") - ) - ) - ); - } - - private User 사용자_등록(String id, String nickname, String email){ - User createUser = User.createUser(id, id, nickname, email, Gender.M, LocalDate.now(), "http://picture.jpg", - true, true, true, Role.USER, "kakao", id, null); - return userRepository.save(createUser); - } - private RecruitmentParticipation 봉사모집글_팀원_등록(Recruitment recruitment, User user){ - RecruitmentParticipation participant = RecruitmentParticipation.createParticipant(recruitment, user, ParticipantState.JOIN_APPROVAL); - return participantRepository.save(participant); - } - private ScheduleParticipation 일정_참여상태_추가(Schedule schedule, RecruitmentParticipation participant, ParticipantState state){ - ScheduleParticipation sp = ScheduleParticipation.createScheduleParticipation(saveSchedule, participant, state); - return scheduleParticipationRepository.save(sp); - } - private String toJson(T data) throws JsonProcessingException { - return objectMapper.writeValueAsString(data); - } - private Image 업로드_이미지_등록(Long no, RealWorkCode code) throws IOException { - ImageParam imageDto = ImageParam.builder() - .code(code) - .no(no) - .uploadImage(getMockMultipartFile()) - .build(); - Long imageNo = imageService.addImage(imageDto); - deleteImageNo.add(imageNo); - - return imageRepository.findById(imageNo).get(); - } - private MockMultipartFile getMockMultipartFile() throws IOException { - return new MockMultipartFile( - "file", "file.PNG", "image/jpg", new FileInputStream("src/main/resources/static/test/file.PNG")); - } -} \ No newline at end of file From 0218e311bc7ecd40058233dff42968d3941e3029 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 20:52:25 +0900 Subject: [PATCH 21/29] =?UTF-8?q?rename:=20RecruitmentParticipationControl?= =?UTF-8?q?ler=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ontroller.java => RecruitmentParticipationController.java} | 2 +- .../global/error/exhadler/ParticipationControllerAdvice.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/java/project/volunteer/domain/recruitmentParticipation/api/{ParticipationController.java => RecruitmentParticipationController.java} (98%) diff --git a/src/main/java/project/volunteer/domain/recruitmentParticipation/api/ParticipationController.java b/src/main/java/project/volunteer/domain/recruitmentParticipation/api/RecruitmentParticipationController.java similarity index 98% rename from src/main/java/project/volunteer/domain/recruitmentParticipation/api/ParticipationController.java rename to src/main/java/project/volunteer/domain/recruitmentParticipation/api/RecruitmentParticipationController.java index ffc22277..f0886a28 100644 --- a/src/main/java/project/volunteer/domain/recruitmentParticipation/api/ParticipationController.java +++ b/src/main/java/project/volunteer/domain/recruitmentParticipation/api/RecruitmentParticipationController.java @@ -16,7 +16,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/recruitment") -public class ParticipationController { +public class RecruitmentParticipationController { private final RecruitmentParticipationFacade participationFacade; @PutMapping("/{recruitmentNo}/join") diff --git a/src/main/java/project/volunteer/global/error/exhadler/ParticipationControllerAdvice.java b/src/main/java/project/volunteer/global/error/exhadler/ParticipationControllerAdvice.java index 5f2281db..698a31a1 100644 --- a/src/main/java/project/volunteer/global/error/exhadler/ParticipationControllerAdvice.java +++ b/src/main/java/project/volunteer/global/error/exhadler/ParticipationControllerAdvice.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import project.volunteer.domain.recruitmentParticipation.api.ParticipationController; +import project.volunteer.domain.recruitmentParticipation.api.RecruitmentParticipationController; import project.volunteer.global.error.exception.BusinessException; import project.volunteer.global.error.exception.ErrorCode; import project.volunteer.global.error.response.BaseErrorResponse; @@ -17,7 +17,7 @@ @Slf4j @RequiredArgsConstructor -@RestControllerAdvice(basePackageClasses = ParticipationController.class) +@RestControllerAdvice(basePackageClasses = RecruitmentParticipationController.class) public class ParticipationControllerAdvice { private final MessageSource ms; From 77fa5be8ed9441a3270b17f3d6b226a62f027a92 Mon Sep 17 00:00:00 2001 From: BonSik Date: Wed, 28 Feb 2024 21:08:58 +0900 Subject: [PATCH 22/29] =?UTF-8?q?test:=20API=20document=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=8C=A8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/project/volunteer/document/ScheduleControllerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/project/volunteer/document/ScheduleControllerTest.java b/src/test/java/project/volunteer/document/ScheduleControllerTest.java index 76e7e163..14175051 100644 --- a/src/test/java/project/volunteer/document/ScheduleControllerTest.java +++ b/src/test/java/project/volunteer/document/ScheduleControllerTest.java @@ -198,7 +198,6 @@ public void detailClosestSchedule() throws Exception { //then result.andExpect(status().isOk()) - .andExpect(jsonPath("activeVolunteerNum").value(0)) .andExpect(jsonPath("state").value(StateResult.AVAILABLE.name())) .andDo(print()) .andDo( @@ -297,7 +296,6 @@ public void detailSchedule() throws Exception { //then result.andExpect(status().isOk()) - .andExpect(jsonPath("activeVolunteerNum").value(0)) .andExpect(jsonPath("state").value(StateResult.AVAILABLE.name())) .andDo(print()) .andDo( From 76f92738e442c90b95d04244962eccc3af3e1658 Mon Sep 17 00:00:00 2001 From: BonSik Date: Sun, 3 Mar 2024 02:24:31 +0900 Subject: [PATCH 23/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20API=20=EC=9D=B8=EC=88=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java new file mode 100644 index 00000000..64b6e545 --- /dev/null +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -0,0 +1,188 @@ +package project.volunteer.acceptance; + +import static io.restassured.RestAssured.given; +import static org.mockito.BDDMockito.given; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_등록; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_팀원_가입_승인; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_팀원_가입_요청; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_등록; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여; + +import java.io.File; +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import project.volunteer.domain.recruitment.domain.VolunteerType; +import project.volunteer.domain.recruitment.domain.VolunteeringCategory; +import project.volunteer.domain.recruitment.domain.VolunteeringType; +import project.volunteer.domain.recruitment.domain.repeatPeriod.Period; +import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; +import project.volunteer.domain.recruitmentParticipation.api.dto.request.ParticipantAddRequest; +import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; +import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; +import project.volunteer.global.common.component.HourFormat; + +public class ScheduleParticipationAcceptanceTest extends AcceptanceTest { + + @DisplayName("봉사 일정 참여에 성공한다.") + @Test + void saveParticipation() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + @DisplayName("일정 참여 인원이 가득찬 경우 참여를 할 수 없다.") + @Test + void saveParticipationWithFullParticipationNum() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + final Long recruitmentParticipationNo2 = 봉사_게시물_팀원_가입_요청(changHoeunToken, recruitmentNo); + final Long recruitmentParticipationNo3 = 봉사_게시물_팀원_가입_요청(bongbongToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1, recruitmentParticipationNo2, recruitmentParticipationNo3)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 2, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여(changHoeunToken, recruitmentNo, scheduleNo); + + given().log().all() + .header(AUTHORIZATION_HEADER, bongbongToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } + + @DisplayName("이미 일정에 참여한 인원은 참여할 수 없다.") + @Test + void saveParticipationDuplicate() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 2, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } + + @DisplayName("시작된 일정에는 참여할 수 없다.") + @Test + void saveParticipationWithDoneSchedule() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 2, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-11T10:00:00Z")); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } + + @DisplayName("봉사 모집글 팀원이 아닌 경우 일정에 참여할 수 없다.") + @Test + void saveParticipationWithNotTeam() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 2, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/join", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()) + .extract(); + } + +} From 73547ee25ae7645b2d832ac8df02a49c10d98d92 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 01:19:01 +0900 Subject: [PATCH 24/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20API=20?= =?UTF-8?q?=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index 64b6e545..64167b28 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -185,4 +185,106 @@ void saveParticipationWithNotTeam() { .extract(); } + @DisplayName("봉사 일정 취소 요청에 성공한다.") + @Test + void cancelParticipation() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancel", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + @DisplayName("봉사 일정에 참여하지 않은 인원은 취소가 불가능하다.") + @Test + void cancelParticipationWithNotParticipant() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancel", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } + + @DisplayName("시작된 일정일 경우, 취소가 불가능하다.") + @Test + void cancelParticipantWithDoneSchedule() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + given(clock.instant()).willReturn(Instant.parse("2024-02-11T10:00:00Z")); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancel", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } + } From bc7ac92129c0f16070e83fd088d0b284a793a2d4 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 01:28:21 +0900 Subject: [PATCH 25/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20=EC=8A=B9?= =?UTF-8?q?=EC=9D=B8=20API=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index 64167b28..7c11c3b0 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -7,19 +7,25 @@ import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_팀원_가입_요청; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_등록; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여_취소요청; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_취소요청_조회; import java.io.File; import java.time.Instant; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import project.volunteer.domain.recruitment.domain.VolunteerType; import project.volunteer.domain.recruitment.domain.VolunteeringCategory; import project.volunteer.domain.recruitment.domain.VolunteeringType; import project.volunteer.domain.recruitment.domain.repeatPeriod.Period; import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; import project.volunteer.domain.recruitmentParticipation.api.dto.request.ParticipantAddRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.global.common.component.HourFormat; @@ -287,4 +293,98 @@ void cancelParticipantWithDoneSchedule() { .extract(); } + @DisplayName("봉사 일정 취소 요청 승인에 성공한다.") + @Test + void approveCancellation() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo); + + List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, + scheduleNo); + + final CancellationApprovalRequest request = new CancellationApprovalRequest( + cancelledParticipantList.stream() + .map(CancelledParticipantDetail::getScheduleParticipationNo) + .collect(Collectors.toList()) + ); + + given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + @DisplayName("봉사 모집글 방장이 아닐 경우, 취소 요청 승인을 할 수 없다.") + @Test + void approveCancellationWithNotOwner(){ + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo); + + List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, + scheduleNo); + + final CancellationApprovalRequest request = new CancellationApprovalRequest( + cancelledParticipantList.stream() + .map(CancelledParticipantDetail::getScheduleParticipationNo) + .collect(Collectors.toList()) + ); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()) + .extract(); + } + } From 4e69ce659395bbf62797b1ce0a407d886b5b5323 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 01:43:23 +0900 Subject: [PATCH 26/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=99=84=EB=A3=8C=20=EC=8A=B9?= =?UTF-8?q?=EC=9D=B8=20API=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 109 +++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index 7c11c3b0..93e5d1a0 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -8,6 +8,8 @@ import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_등록; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여_취소요청; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여완료_승인; +import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_참여완료_조회; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_일정_취소요청_조회; import java.io.File; @@ -25,7 +27,9 @@ import project.volunteer.domain.recruitment.domain.repeatPeriod.Week; import project.volunteer.domain.recruitmentParticipation.api.dto.request.ParticipantAddRequest; import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; +import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.global.common.component.HourFormat; @@ -321,7 +325,7 @@ void approveCancellation() { 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo); - List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, + final List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, scheduleNo); final CancellationApprovalRequest request = new CancellationApprovalRequest( @@ -342,7 +346,7 @@ void approveCancellation() { @DisplayName("봉사 모집글 방장이 아닐 경우, 취소 요청 승인을 할 수 없다.") @Test - void approveCancellationWithNotOwner(){ + void approveCancellationWithNotOwner() { given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, @@ -368,7 +372,7 @@ void approveCancellationWithNotOwner(){ 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo); - List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, + final List cancelledParticipantList = 봉사_일정_취소요청_조회(bonsikToken, recruitmentNo, scheduleNo); final CancellationApprovalRequest request = new CancellationApprovalRequest( @@ -385,6 +389,103 @@ void approveCancellationWithNotOwner(){ .then().log().all() .statusCode(HttpStatus.FORBIDDEN.value()) .extract(); - } + } + + @DisplayName("일정 참여 완료 승인에 성공한다.") + @Test + void approveParticipationCompletion() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + given(clock.instant()).willReturn(Instant.parse("2024-02-11T10:00:00Z")); + 봉사_일정_참여완료_미승인_스케줄링(); + + final List completedParticipantList = 봉사_일정_참여완료_조회(bonsikToken, recruitmentNo, + scheduleNo); + + final ParticipationCompletionApproveRequest request = new ParticipationCompletionApproveRequest( + completedParticipantList.stream() + .map(CompletedParticipantDetail::getScheduleParticipationNo) + .collect(Collectors.toList()) + ); + + given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/complete", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + @DisplayName("이미 참여 완료 승인된 인원은 재승인을 할 수 없다.") + @Test + void approveParticipationCompletionWithInvalidState() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + + given(clock.instant()).willReturn(Instant.parse("2024-02-11T10:00:00Z")); + 봉사_일정_참여완료_미승인_스케줄링(); + + final List completedParticipantList = 봉사_일정_참여완료_조회(bonsikToken, recruitmentNo, + scheduleNo); + + final ParticipationCompletionApproveRequest approveRequest = new ParticipationCompletionApproveRequest( + completedParticipantList.stream() + .map(CompletedParticipantDetail::getScheduleParticipationNo) + .collect(Collectors.toList()) + ); + 봉사_일정_참여완료_승인(bonsikToken, recruitmentNo, scheduleNo, approveRequest); + + given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(approveRequest) + .when().put("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/complete", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract(); + } } From 41b0ec118bff814cc989152a4262cde51f9284e9 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 01:53:24 +0900 Subject: [PATCH 27/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=9D=B8=EC=88=98=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index 93e5d1a0..3069c60f 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -1,6 +1,7 @@ package project.volunteer.acceptance; import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_등록; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_팀원_가입_승인; @@ -16,6 +17,7 @@ import java.time.Instant; import java.util.List; import java.util.stream.Collectors; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -28,6 +30,8 @@ import project.volunteer.domain.recruitmentParticipation.api.dto.request.ParticipantAddRequest; import project.volunteer.domain.scheduleParticipation.api.dto.CancellationApprovalRequest; import project.volunteer.domain.scheduleParticipation.api.dto.ParticipationCompletionApproveRequest; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; @@ -488,4 +492,83 @@ void approveParticipationCompletionWithInvalidState() { .extract(); } + @DisplayName("봉사 일정 참여 리스트 조회에 성공한다.") + @Test + void findActiveParticipant() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + final Long recruitmentParticipationNo2 = 봉사_게시물_팀원_가입_요청(bongbongToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1, recruitmentParticipationNo2)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여(bongbongToken, recruitmentNo, scheduleNo); + + List response = given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .when() + .get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/participating", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(ActiveParticipantsSearchResult.class) + .getParticipating(); + assertThat(response).hasSize(2) + .extracting("nickname") + .containsExactlyInAnyOrder("soeun", "bongbong"); + } + + @DisplayName("방장이 아닐 경우, 참여 리스트를 조회할 수 없다.") + @Test + void findActiveParticipantWithNotOwner() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + final Long recruitmentParticipationNo2 = 봉사_게시물_팀원_가입_요청(bongbongToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1, recruitmentParticipationNo2)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여(bongbongToken, recruitmentNo, scheduleNo); + + given().log().all() + .header(AUTHORIZATION_HEADER, soeunToken) + .when() + .get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/participating", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.FORBIDDEN.value()) + .extract(); + } + } From 5a6b3040f35bbde48d2296fb832fadf3968a6c53 Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 01:57:27 +0900 Subject: [PATCH 28/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index 3069c60f..f087637e 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -17,7 +17,6 @@ import java.time.Instant; import java.util.List; import java.util.stream.Collectors; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; @@ -33,6 +32,7 @@ import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.ActiveParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; @@ -571,4 +571,49 @@ void findActiveParticipantWithNotOwner() { .extract(); } + @DisplayName("봉사 일정 취소 요청 리스트 조회에 성공한다.") + @Test + void findCancelledParticipant() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + final Long recruitmentParticipationNo2 = 봉사_게시물_팀원_가입_요청(bongbongToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1, recruitmentParticipationNo2)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여(bongbongToken, recruitmentNo, scheduleNo); + + 봉사_일정_참여_취소요청(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여_취소요청(bongbongToken, recruitmentNo, scheduleNo); + + List response = given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .when() + .get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/cancelling", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(CancelledParticipantsSearchResult.class) + .getCancelling(); + assertThat(response).hasSize(2) + .extracting("nickname") + .containsExactlyInAnyOrder("soeun", "bongbong"); + } + } From e041c1c41072c5dca0371be6e8c32a34f8b8d31c Mon Sep 17 00:00:00 2001 From: BonSik Date: Mon, 4 Mar 2024 02:01:32 +0900 Subject: [PATCH 29/29] =?UTF-8?q?test:=20=EB=B4=89=EC=82=AC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B0=B8=EC=97=AC=20=EC=99=84=EB=A3=8C=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EC=9D=B8?= =?UTF-8?q?=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleParticipationAcceptanceTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java index f087637e..d5900928 100644 --- a/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java +++ b/src/test/java/project/volunteer/acceptance/ScheduleParticipationAcceptanceTest.java @@ -2,6 +2,7 @@ import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.BDDMockito.given; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_등록; import static project.volunteer.acceptance.AcceptanceFixtures.봉사_게시물_팀원_가입_승인; @@ -34,6 +35,7 @@ import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantDetail; import project.volunteer.domain.scheduleParticipation.service.dto.CancelledParticipantsSearchResult; import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantDetail; +import project.volunteer.domain.scheduleParticipation.service.dto.CompletedParticipantsSearchResult; import project.volunteer.domain.sehedule.api.dto.request.ScheduleAddressRequest; import project.volunteer.domain.sehedule.api.dto.request.ScheduleUpsertRequest; import project.volunteer.global.common.component.HourFormat; @@ -616,4 +618,52 @@ void findCancelledParticipant() { .containsExactlyInAnyOrder("soeun", "bongbong"); } + @DisplayName("봉사 일정 참여 완료(미승인, 승인) 리스트 조회에 성공한다.") + @Test + void findCompletedParticipant() { + given(clock.instant()).willReturn(Instant.parse("2024-01-29T10:00:00Z")); + + final Long recruitmentNo = 봉사_게시물_등록(bonsikToken, + VolunteeringCategory.EDUCATION, "unicef", "11", "1111", "detail", "fullName", 3.2F, 3.2F, true, + VolunteerType.ADULT, 10, VolunteeringType.IRREG, "01-01-2024", "02-01-2024", HourFormat.AM, "10:00", + 10, Period.NONE, Week.NONE, List.of(), "title", "content", true, false, + new File("src/main/resources/static/test/file.PNG")); + + final Long recruitmentParticipationNo1 = 봉사_게시물_팀원_가입_요청(soeunToken, recruitmentNo); + final Long recruitmentParticipationNo2 = 봉사_게시물_팀원_가입_요청(bongbongToken, recruitmentNo); + + final ParticipantAddRequest participantAddRequest = new ParticipantAddRequest( + List.of(recruitmentParticipationNo1, recruitmentParticipationNo2)); + 봉사_게시물_팀원_가입_승인(bonsikToken, recruitmentNo, participantAddRequest); + + given(clock.instant()).willReturn(Instant.parse("2024-02-05T10:00:00Z")); + + final ScheduleUpsertRequest scheduleUpsertRequest = new ScheduleUpsertRequest( + new ScheduleAddressRequest("1", "1111", "1111", "1111"), "02-10-2024", "AM", "10:00", 2, + "unicef", 10, "content"); + final Long scheduleNo = 봉사_일정_등록(bonsikToken, recruitmentNo, scheduleUpsertRequest); + + 봉사_일정_참여(soeunToken, recruitmentNo, scheduleNo); + 봉사_일정_참여(bongbongToken, recruitmentNo, scheduleNo); + + given(clock.instant()).willReturn(Instant.parse("2024-02-11T10:00:00Z")); + 봉사_일정_참여완료_미승인_스케줄링(); + + List response = given().log().all() + .header(AUTHORIZATION_HEADER, bonsikToken) + .when() + .get("/recruitment/{recruitmentNo}/schedule/{scheduleNo}/completion", recruitmentNo, scheduleNo) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(CompletedParticipantsSearchResult.class) + .getDone(); + assertThat(response).hasSize(2) + .extracting("nickname", "isApproved") + .containsExactlyInAnyOrder( + tuple("soeun", false), + tuple("bongbong", false) + ); + } + }