-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 예약 조기종료 기능 구현 #969
base: dev
Are you sure you want to change the base?
feat: 예약 조기종료 기능 구현 #969
Changes from all commits
34a3798
2265a54
5e10d61
d0dea33
ed0580a
e19af2e
565b680
cb43e8e
c75ad9e
25cbf2f
eaa505b
d75d31b
76d3464
0f5e9bb
d910d49
bf0b974
aaeb59e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,28 @@ | ||
package com.woowacourse.zzimkkong.domain; | ||
|
||
import com.woowacourse.zzimkkong.exception.reservation.InvalidMinimumDurationTimeInEarlyStopException; | ||
import com.woowacourse.zzimkkong.exception.reservation.NotCurrentReservationException; | ||
import com.woowacourse.zzimkkong.exception.setting.NoMatchingSettingException; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import net.logstash.logback.encoder.org.apache.commons.lang3.StringUtils; | ||
|
||
import javax.persistence.*; | ||
import javax.persistence.Column; | ||
import javax.persistence.Embedded; | ||
import javax.persistence.Entity; | ||
import javax.persistence.FetchType; | ||
import javax.persistence.ForeignKey; | ||
import javax.persistence.GeneratedValue; | ||
import javax.persistence.GenerationType; | ||
import javax.persistence.Id; | ||
import javax.persistence.Index; | ||
import javax.persistence.JoinColumn; | ||
import javax.persistence.ManyToOne; | ||
import javax.persistence.Table; | ||
import java.time.DayOfWeek; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.temporal.ChronoUnit; | ||
|
||
@Getter | ||
@Builder | ||
|
@@ -82,6 +96,10 @@ public void update(final Reservation updateReservation) { | |
this.space = updateReservation.space; | ||
} | ||
|
||
public void updateReservationTime(final ReservationTime updateReservationTime) { | ||
this.reservationTime = updateReservationTime; | ||
} | ||
|
||
public LocalDateTime getStartTime() { | ||
return reservationTime.getStartTime(); | ||
} | ||
|
@@ -142,4 +160,36 @@ public String getUserName() { | |
} | ||
return this.userName; | ||
} | ||
|
||
public TimeUnit getReservationTimeUnit() { | ||
Setting setting = space.getSpaceSettings().getSettings() | ||
.stream() | ||
.filter(it -> it.supports(getTimeSlot(), getDayOfWeek())) | ||
.findFirst() | ||
.orElseThrow(NoMatchingSettingException::new); | ||
|
||
return setting.getReservationTimeUnit(); | ||
} | ||
|
||
public void doEarlyClose(final LocalDateTime now, final Map map) { | ||
validateEarlyCloseable(now); | ||
|
||
int endTimeFloor = this.getReservationTimeUnit().floor(now); | ||
|
||
this.reservationTime = ReservationTime.of( | ||
reservationTime.getStartTime(), | ||
now.withMinute(endTimeFloor), | ||
map.getServiceZone(), | ||
false); | ||
} | ||
|
||
private void validateEarlyCloseable(final LocalDateTime now) { | ||
if (!this.isInUse(now)) { | ||
throw new NotCurrentReservationException(); | ||
} | ||
|
||
if (ChronoUnit.MINUTES.between(this.getStartTime(), now) < this.getReservationTimeUnit().getMinutes()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
throw new InvalidMinimumDurationTimeInEarlyStopException(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,4 +98,3 @@ public String toString() { | |
return startTime + " ~ " + endTimeAsString; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
import javax.persistence.Column; | ||
import javax.persistence.Embeddable; | ||
import java.time.LocalDateTime; | ||
import java.time.LocalTime; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
@@ -62,6 +63,10 @@ public boolean isShorterThan(final TimeUnit that) { | |
return this.minutes < that.minutes; | ||
} | ||
|
||
public int floor(final LocalDateTime time) { | ||
return time.getMinute() / this.minutes * this.minutes; | ||
} | ||
Comment on lines
+66
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏿👍🏿👍🏿👍🏿 |
||
|
||
@Override | ||
public String toString() { | ||
int hours = this.minutes / 60; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,73 @@ | ||||||
package com.woowacourse.zzimkkong.dto.reservation; | ||||||
|
||||||
import com.woowacourse.zzimkkong.domain.ReservationType; | ||||||
import com.woowacourse.zzimkkong.dto.member.LoginUserEmail; | ||||||
import lombok.Getter; | ||||||
import lombok.NoArgsConstructor; | ||||||
|
||||||
@Getter | ||||||
@NoArgsConstructor | ||||||
public class ReservationEarlyStopDto { | ||||||
|
||||||
private Long mapId; | ||||||
private Long spaceId; | ||||||
private Long reservationId; | ||||||
private String email; | ||||||
private String password; | ||||||
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. email 필드는 조기종료 기능에서는 따로 받을 필요 없을 것 같아요~ 이미 Controller 에서 좀 헷갈릴 수 있어서 추가 설명 드리면, 다른 DTO 들에서 보이는 email 필드는 공간 관리자 MANAGER (e.g. 잠실, 선릉 맵 매니저 = 포비, 제이슨 등) 가 다른 회원들의 예약을 대신 매니징해줄 때 이메일이 필요한 경우 (회원 유저)가 있어서 따로 받기 위해서 별도로 존재하는 필드라고 생각해주시면 됩니다. |
||||||
private LoginUserEmail loginUserEmail; | ||||||
private ReservationType reservationType; | ||||||
|
||||||
private ReservationEarlyStopDto( | ||||||
final Long mapId, | ||||||
final Long spaceId, | ||||||
final Long reservationId, | ||||||
final ReservationEarlyStopRequest request, | ||||||
final LoginUserEmail loginUserEmail, | ||||||
final String apiType) { | ||||||
this.reservationId = reservationId; | ||||||
this.spaceId = spaceId; | ||||||
this.mapId = mapId; | ||||||
this.password = request.getPassword(); | ||||||
this.email = request.getEmail(); | ||||||
this.loginUserEmail = loginUserEmail; | ||||||
this.reservationType = ReservationType.of(apiType, getReservationEmail(apiType)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
매니저 Controller 쪽에도 동일한 API를 뚫을 게 아니라면 이렇게 고정되어도 괜찮겠네요. DTO 생성 시 불필요하게 주입받는 인자도 줄이고. 저희 기존 코드 틀을 지켜주시려고 신경을 많이 써주시다 보니 불필요하게 레퍼런스 되는 부분이 있는 것 같아요. 그래서 코드 이해를 도와드리고 사고를 더 확장? 시켜드리기 위해 드리는 제안입니다 ㅎㅎ. 확장하기 용이한 지금 이 형태가 좋다면 꼭 반영 안하셔도 됩니다! 회의 때 여러분이 나눈 의견에 더 적합한 방향으로 구현 해주세요. |
||||||
} | ||||||
|
||||||
public static ReservationEarlyStopDto of( | ||||||
final Long mapId, | ||||||
final Long spaceId, | ||||||
final Long reservationId, | ||||||
final ReservationEarlyStopRequest request, | ||||||
final LoginUserEmail loginUserEmail, | ||||||
final String apiType) { | ||||||
return new ReservationEarlyStopDto( | ||||||
mapId, | ||||||
spaceId, | ||||||
reservationId, | ||||||
request, | ||||||
loginUserEmail, | ||||||
apiType); | ||||||
} | ||||||
|
||||||
public static ReservationEarlyStopDto of( | ||||||
final Long mapId, | ||||||
final Long spaceId, | ||||||
final Long reservationId, | ||||||
final ReservationEarlyStopRequest request, | ||||||
final String apiType) { | ||||||
return new ReservationEarlyStopDto( | ||||||
mapId, | ||||||
spaceId, | ||||||
reservationId, | ||||||
request, | ||||||
new LoginUserEmail(), | ||||||
apiType); | ||||||
} | ||||||
|
||||||
private LoginUserEmail getReservationEmail(String apiType) { | ||||||
if (ReservationType.Constants.GUEST.equals(apiType)) { | ||||||
return this.loginUserEmail; | ||||||
} | ||||||
return LoginUserEmail.from(this.email); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.woowacourse.zzimkkong.dto.reservation; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
import javax.validation.constraints.Pattern; | ||
import javax.validation.constraints.Size; | ||
|
||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.DESCRIPTION_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.EMPTY_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.NAME_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.NAMING_FORMAT; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.RESERVATION_PW_FORMAT; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.RESERVATION_PW_MESSAGE; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class ReservationEarlyStopRequest { | ||
@Pattern(regexp = RESERVATION_PW_FORMAT, message = RESERVATION_PW_MESSAGE) | ||
private String password; | ||
|
||
@Pattern(regexp = NAMING_FORMAT, message = NAME_MESSAGE) | ||
protected String name; | ||
|
||
@NotBlank(message = EMPTY_MESSAGE) | ||
@Size(max = 100, message = DESCRIPTION_MESSAGE) | ||
protected String description; | ||
|
||
public String getPassword() { | ||
return null; | ||
} | ||
|
||
public String getEmail() { | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.woowacourse.zzimkkong.dto.reservation; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
import javax.validation.constraints.Pattern; | ||
import javax.validation.constraints.Size; | ||
|
||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.DESCRIPTION_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.EMPTY_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.NAME_MESSAGE; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.NAMING_FORMAT; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.RESERVATION_PW_FORMAT; | ||
import static com.woowacourse.zzimkkong.dto.ValidatorMessage.RESERVATION_PW_MESSAGE; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class ReservationManagerEarlyStopRequest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하는 곳이 없네요? |
||
@Pattern(regexp = RESERVATION_PW_FORMAT, message = RESERVATION_PW_MESSAGE) | ||
private String password; | ||
|
||
@Pattern(regexp = NAMING_FORMAT, message = NAME_MESSAGE) | ||
protected String name; | ||
|
||
@NotBlank(message = EMPTY_MESSAGE) | ||
@Size(max = 100, message = DESCRIPTION_MESSAGE) | ||
protected String description; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,13 @@ | ||||||
package com.woowacourse.zzimkkong.exception.reservation; | ||||||
|
||||||
import com.woowacourse.zzimkkong.exception.ZzimkkongException; | ||||||
import org.springframework.http.HttpStatus; | ||||||
|
||||||
public class InvalidMinimumDurationTimeInEarlyStopException extends ZzimkkongException { | ||||||
|
||||||
private static final String MESSAGE = "조기 종료는 최소 5분 이후부터 가능합니다."; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
분이 가변적이어야 할 것 같아요~ Exception 생성자에 TimeUnit 관련 인자를 받아야겠네요. 참고할 만한 Exception 많으니 한 번 둘러보셔서 참고하시면 될 것 같습니다! |
||||||
|
||||||
public InvalidMinimumDurationTimeInEarlyStopException() { | ||||||
super(MESSAGE, HttpStatus.BAD_REQUEST); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.woowacourse.zzimkkong.exception.reservation; | ||
|
||
import com.woowacourse.zzimkkong.exception.ZzimkkongException; | ||
import org.springframework.http.HttpStatus; | ||
|
||
public class NotCurrentReservationException extends ZzimkkongException { | ||
private static final String MESSAGE = "사용중인 예약에 대해서만 조기종료가 가능합니다."; | ||
|
||
public NotCurrentReservationException() { | ||
super(MESSAGE, HttpStatus.BAD_REQUEST); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.woowacourse.zzimkkong.exception.setting; | ||
|
||
import com.woowacourse.zzimkkong.exception.ZzimkkongException; | ||
import org.springframework.http.HttpStatus; | ||
|
||
public class NoMatchingSettingException extends ZzimkkongException { | ||
private static final String MESSAGE = "일치하는 설정이 존재하지 않습니다."; | ||
|
||
public NoMatchingSettingException() { | ||
super(MESSAGE, HttpStatus.NOT_FOUND); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 500과 같은 서버 에러가 적합해 보여요. setting 은 무조건 매칭되는 한개가 있어야합니다. 만약 없다면 모종의 이유로 발생한 데이터 이슈입니다. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reservation 안에서만 쓰이는 것으로 보여요