Skip to content

Commit

Permalink
Merge pull request #23 from dev-hooon/feat/#4
Browse files Browse the repository at this point in the history
feat : 예약 선점 로직 구현
  • Loading branch information
dlswns2480 authored Dec 28, 2023
2 parents 98e958c + bd8a90f commit 492d637
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 3 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ dependencies {
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
NOT_EXIST_MEMBER("존재하지 않는 아이디입니다.");
NOT_EXIST_MEMBER("존재하지 않는 아이디입니다."),
ALREADY_PREOCCUPIED_RESERVATION_TIME("이미 타인에게 선점권이 있는 예약시간입니다."),
ALREADY_OCCUPIED_RESERVATION_TIME("이미 예약된 시간입니다."),
NOT_EXIST_SHOP("존재하지 않는 매장입니다."),
NOT_EXIST_TIME("존재하지 않는 예약 시간입니다.");

private final String message;
}
35 changes: 35 additions & 0 deletions src/main/java/com/prgrms/catchtable/facade/ReservationFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.prgrms.catchtable.facade;

import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
import com.prgrms.catchtable.reservation.dto.response.CreateReservationResponse;
import com.prgrms.catchtable.reservation.service.ReservationAsync;
import com.prgrms.catchtable.reservation.service.ReservationService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ReservationFacade {

private final ReservationService reservationService;
private final ReservationAsync reservationAsync;

public CreateReservationResponse preOccupyReservation(
CreateReservationRequest request) {
ReservationTime reservationTime = reservationService.validateReservationAndSave(
request);

String shopName = reservationTime.getShop().getName();

reservationAsync.setPreOcuppied(reservationTime);

return CreateReservationResponse.builder()
.shopName(shopName)
.memberName("memberA")
.date(reservationTime.getTime())
.peopleCount(request.peopleCount())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.prgrms.catchtable.reservation.controller;

import com.prgrms.catchtable.facade.ReservationFacade;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
import com.prgrms.catchtable.reservation.dto.response.CreateReservationResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/reservations")
@RequiredArgsConstructor
public class ReservationController {

private final ReservationFacade reservationFacade;

@PostMapping
public ResponseEntity<CreateReservationResponse> createReservationResponse(
CreateReservationRequest request) {
return ResponseEntity.ok(reservationFacade.preOccupyReservation(request));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ public class Reservation extends BaseEntity {

@OneToOne(fetch = LAZY)
@JoinColumn(name = "reservation_time_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private ReservationTime time;
private ReservationTime reservationTime;

@Builder
public Reservation(ReservationStatus status, int peopleCount) {
public Reservation(ReservationStatus status, int peopleCount, Shop shop,
ReservationTime reservationTime) {
this.status = status;
this.peopleCount = peopleCount;
this.shop = shop;
this.reservationTime = reservationTime;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class ReservationTime {
@Column(name = "is_occupied")
private boolean isOccupied;

@Column(name = "is_pre_occupied")
private boolean isPreOccupied;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Shop shop;
Expand All @@ -43,4 +46,16 @@ public ReservationTime(LocalDateTime time) {
this.time = time;
this.isOccupied = false;
}

public void reverseOccupied() {
this.isOccupied = !this.isOccupied;
}

public void reversePreOccupied() {
this.isPreOccupied = !this.isPreOccupied;
}

public void insertShop(Shop shop) {
this.shop = shop;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.prgrms.catchtable.reservation.dto.request;

import lombok.Builder;

@Builder
public record CreateReservationRequest(Long reservationTimeId,
int peopleCount) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.prgrms.catchtable.reservation.dto.response;

import java.time.LocalDateTime;
import lombok.Builder;

@Builder
public record CreateReservationResponse(String shopName,
String memberName,
LocalDateTime date,
int peopleCount) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.prgrms.catchtable.reservation.repository;

import com.prgrms.catchtable.reservation.domain.Reservation;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReservationRepository extends JpaRepository<Reservation, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.prgrms.catchtable.reservation.repository;

import com.prgrms.catchtable.reservation.domain.ReservationTime;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReservationTimeRepository extends JpaRepository<ReservationTime, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.prgrms.catchtable.reservation.service;

import com.prgrms.catchtable.reservation.domain.ReservationTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
public class ReservationAsync {

@Transactional
public void setPreOcuppied(ReservationTime reservationTime) {
reservationTime.reversePreOccupied();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(reservationTime::reversePreOccupied, 10, TimeUnit.SECONDS);

scheduler.shutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.prgrms.catchtable.reservation.service;

import static com.prgrms.catchtable.common.exception.ErrorCode.ALREADY_PREOCCUPIED_RESERVATION_TIME;
import static com.prgrms.catchtable.common.exception.ErrorCode.NOT_EXIST_TIME;

import com.prgrms.catchtable.common.exception.custom.BadRequestCustomException;
import com.prgrms.catchtable.common.exception.custom.NotFoundCustomException;
import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
import com.prgrms.catchtable.reservation.repository.ReservationTimeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ReservationService {

private final ReservationTimeRepository reservationTimeRepository;

@Transactional
public ReservationTime validateReservationAndSave(CreateReservationRequest request) {
ReservationTime reservationTime = reservationTimeRepository.findById(
request.reservationTimeId())
.orElseThrow(() -> new NotFoundCustomException(NOT_EXIST_TIME));

if (reservationTime.isPreOccupied()) {
throw new BadRequestCustomException(ALREADY_PREOCCUPIED_RESERVATION_TIME);
}

return reservationTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.prgrms.catchtable.common.data.reservation;

import static com.prgrms.catchtable.reservation.domain.ReservationStatus.COMPLETED;

import com.prgrms.catchtable.common.data.shop.ShopData;
import com.prgrms.catchtable.reservation.domain.Reservation;
import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
import java.time.LocalDateTime;

public class ReservationData {

public static Reservation getReservation() {
return Reservation.builder()
.status(COMPLETED)
.peopleCount(4)
.reservationTime(getReservationTimeNotPreOccupied())
.build();
}

public static ReservationTime getReservationTimeNotPreOccupied() {
ReservationTime reservationTime = ReservationTime.builder()
.time(LocalDateTime.of(2024, 12, 31, 19, 30))
.build();
reservationTime.insertShop(ShopData.getShop());
return reservationTime;
}

public static ReservationTime getReservationTimePreOccupied() {
ReservationTime reservationTime = ReservationTime.builder()
.time(LocalDateTime.of(2024, 12, 31, 19, 30))
.build();
reservationTime.insertShop(ShopData.getShop());
reservationTime.reversePreOccupied();
return reservationTime;
}

public static CreateReservationRequest getCreateReservationRequest() {
return CreateReservationRequest.builder()
.reservationTimeId(1L)
.peopleCount(4)
.build();
}

}
20 changes: 20 additions & 0 deletions src/test/java/com/prgrms/catchtable/common/data/shop/ShopData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.prgrms.catchtable.common.data.shop;

import static com.prgrms.catchtable.shop.domain.Category.JAPANESE_FOOD;

import com.prgrms.catchtable.shop.domain.Address;
import com.prgrms.catchtable.shop.domain.Shop;
import java.math.BigDecimal;

public class ShopData {

public static Shop getShop() {
return Shop.builder()
.name("shopA")
.rating(BigDecimal.valueOf(5L))
.category(JAPANESE_FOOD)
.address(Address.builder().build())
.capacity(30)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.prgrms.catchtable.facade;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import com.prgrms.catchtable.common.data.reservation.ReservationData;
import com.prgrms.catchtable.reservation.domain.ReservationTime;
import com.prgrms.catchtable.reservation.dto.request.CreateReservationRequest;
import com.prgrms.catchtable.reservation.dto.response.CreateReservationResponse;
import com.prgrms.catchtable.reservation.service.ReservationAsync;
import com.prgrms.catchtable.reservation.service.ReservationService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ReservationFacadeTest {

@Mock
private ReservationAsync reservationAsync;
@Mock
private ReservationService reservationService;
@InjectMocks
private ReservationFacade reservationFacade;

@Test
@DisplayName("예약을 검증하고 선점권을 true로 바꾸는 것에 성공한다.")
void preOccupyReservation() {
ReservationTime reservationTime = ReservationData.getReservationTimeNotPreOccupied();
CreateReservationRequest request = ReservationData.getCreateReservationRequest();

when(reservationService.validateReservationAndSave(
any(CreateReservationRequest.class))).thenReturn(reservationTime);

CreateReservationResponse response = reservationFacade.preOccupyReservation(
request);

assertAll(
() -> assertThat(response.date()).isEqualTo(reservationTime.getTime()),
() -> assertThat(response.peopleCount()).isEqualTo(request.peopleCount())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.prgrms.catchtable.reservation.domain;

import static org.assertj.core.api.Assertions.assertThat;

import com.prgrms.catchtable.common.data.reservation.ReservationData;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class ReservationTimeTest {

@Test
@DisplayName("예약 선점 여부 변경에 성공한다")
void reversePreOccupied() {
ReservationTime reservationTime = ReservationData.getReservationTimeNotPreOccupied();
reservationTime.reversePreOccupied();

assertThat(reservationTime.isPreOccupied()).isTrue();
}

@Test
@DisplayName("예약 여부 변경에 성공한다.")
void reverseOccupied() {
ReservationTime reservationTime = ReservationData.getReservationTimeNotPreOccupied();
reservationTime.reverseOccupied();

assertThat(reservationTime.isOccupied()).isTrue();
}
}
Loading

0 comments on commit 492d637

Please sign in to comment.