Skip to content

Commit

Permalink
feat: 시험 차량 예약 기능 구현 (#21)
Browse files Browse the repository at this point in the history
* refactor: 디렉토리 구조 변경 #8

* feat: 차량 예약 조회 및 예약 기능 구현 #8
  • Loading branch information
gengminy authored Dec 6, 2023
1 parent 6fcf7c4 commit 09a6a21
Show file tree
Hide file tree
Showing 36 changed files with 396 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.testcar.car.common.annotation.RoleAllowed;
import com.testcar.car.common.response.PageResponse;
import com.testcar.car.domains.car.entity.Car;
import com.testcar.car.domains.car.model.CarResponse;
import com.testcar.car.domains.car.model.RegisterCarRequest;
import com.testcar.car.domains.car.model.vo.CarFilterCondition;
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/testcar/car/domains/car/CarService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.testcar.car.common.exception.BadRequestException;
import com.testcar.car.common.exception.NotFoundException;
import com.testcar.car.domains.car.entity.Car;
import com.testcar.car.domains.car.exception.ErrorCode;
import com.testcar.car.domains.car.model.RegisterCarRequest;
import com.testcar.car.domains.car.model.vo.CarFilterCondition;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.testcar.car.domains.car;
package com.testcar.car.domains.car.entity;


import com.testcar.car.common.entity.BaseEntity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.testcar.car.domains.car;
package com.testcar.car.domains.car.entity;


import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


import com.testcar.car.common.annotation.DateTimeFormat;
import com.testcar.car.domains.car.Car;
import com.testcar.car.domains.car.Type;
import com.testcar.car.domains.car.entity.Car;
import com.testcar.car.domains.car.entity.Type;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.testcar.car.domains.car.model;


import com.testcar.car.domains.car.Type;
import com.testcar.car.domains.car.entity.Type;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


import com.testcar.car.common.annotation.DateTimeFormat;
import com.testcar.car.domains.car.Type;
import com.testcar.car.domains.car.entity.Type;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.testcar.car.domains.car.repository;


import com.testcar.car.domains.car.Car;
import com.testcar.car.domains.car.entity.Car;
import com.testcar.car.domains.car.model.vo.CarFilterCondition;
import java.util.Optional;
import org.springframework.data.domain.Page;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.testcar.car.domains.car.repository;

import static com.testcar.car.domains.car.QCar.car;
import static com.testcar.car.domains.car.entity.QCar.car;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.testcar.car.common.entity.BaseQueryDslRepository;
import com.testcar.car.domains.car.Car;
import com.testcar.car.domains.car.Type;
import com.testcar.car.domains.car.entity.Car;
import com.testcar.car.domains.car.entity.Type;
import com.testcar.car.domains.car.model.vo.CarFilterCondition;
import java.time.LocalDateTime;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.testcar.car.domains.car.repository;


import com.testcar.car.domains.car.Car;
import com.testcar.car.domains.car.entity.Car;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.testcar.car.domains.carReservation;


import com.testcar.car.common.annotation.AuthMember;
import com.testcar.car.common.annotation.RoleAllowed;
import com.testcar.car.common.response.PageResponse;
import com.testcar.car.domains.carReservation.entity.CarReservation;
import com.testcar.car.domains.carReservation.model.CarReservationResponse;
import com.testcar.car.domains.carReservation.model.dto.CarReservationDto;
import com.testcar.car.domains.carReservation.model.vo.CarReservationFilterCondition;
import com.testcar.car.domains.member.Member;
import com.testcar.car.domains.member.Role;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "[시험차량 관리] ", description = "시험차량 관리 API")
@RestController
@RequestMapping("/cars")
@RequiredArgsConstructor
public class CarReservationController {
private final CarReservationService carReservationService;

@GetMapping("/reservations")
@RoleAllowed(role = Role.USER)
@Operation(summary = "[시험차량 관리] 시험차량 대여 이력", description = "조건에 맞는 시험차량 대여 이력을 모두 조회합니다.")
public PageResponse<CarReservationResponse> getCarReservationsByCondition(
@ParameterObject @ModelAttribute CarReservationFilterCondition condition,
@ParameterObject Pageable pageable) {
final Page<CarReservationDto> carReservations =
carReservationService.findAllPageByCondition(condition, pageable);
return PageResponse.from(carReservations.map(CarReservationResponse::from));
}

@PostMapping("/{carStockId}/reserve")
@RoleAllowed(role = Role.USER)
@Operation(summary = "[시험차량 관리] 시험차량 대여", description = "시험 차량을 예약합니다.")
public CarReservationResponse postCarReservation(
@AuthMember Member member, @PathVariable Long carStockId) {
final CarReservation carReservation = carReservationService.reserve(member, carStockId);
return CarReservationResponse.from(carReservation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.testcar.car.domains.carReservation;

import static com.testcar.car.domains.carStock.entity.StockStatus.AVAILABLE;

import com.testcar.car.common.exception.BadRequestException;
import com.testcar.car.domains.carReservation.entity.CarReservation;
import com.testcar.car.domains.carReservation.exception.ErrorCode;
import com.testcar.car.domains.carReservation.model.dto.CarReservationDto;
import com.testcar.car.domains.carReservation.model.vo.CarReservationFilterCondition;
import com.testcar.car.domains.carReservation.repository.CarReservationRepository;
import com.testcar.car.domains.carStock.CarStockService;
import com.testcar.car.domains.carStock.entity.CarStock;
import com.testcar.car.domains.member.Member;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class CarReservationService {
private static final int RESERVATION_DATE = 7;

private final CarStockService carStockService;
private final CarReservationRepository carReservationRepository;

/** 조건에 맞는 시험차량 대여 이력을 조회합니다. */
public Page<CarReservationDto> findAllPageByCondition(
CarReservationFilterCondition condition, Pageable pageable) {
return carReservationRepository.findAllPageByCondition(condition, pageable);
}

/** 시험차량을 예약합니다. */
public CarReservation reserve(Member member, Long carStockId) {
final CarStock carStock = carStockService.findById(carStockId);
validateCarStockAvailable(carStock);
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime expiredAt = now.toLocalDate().plusDays(RESERVATION_DATE).atStartOfDay();
final CarReservation carReservation =
CarReservation.builder()
.member(member)
.carStock(carStock)
.startedAt(now)
.expiredAt(expiredAt)
.build();
return carReservationRepository.save(carReservation);
}

public void validateCarStockAvailable(CarStock carStock) {
if (carStock.getStatus() != AVAILABLE) {
throw new BadRequestException(ErrorCode.CAR_STOCK_NOT_AVAILABLE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.testcar.car.domains.carReservation;
package com.testcar.car.domains.carReservation.entity;


import com.testcar.car.common.entity.BaseEntity;
import com.testcar.car.domains.carStock.CarStock;
import com.testcar.car.domains.carStock.entity.CarStock;
import com.testcar.car.domains.member.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -41,9 +41,13 @@ public class CarReservation extends BaseEntity {
@JoinColumn(name = "carStockId", nullable = false)
private CarStock carStock;

// 대여시각
// 대여 시각
@Column(nullable = false)
private LocalDateTime reservedAt;
private LocalDateTime startedAt;

// 대여 만료시각
@Column(nullable = false)
private LocalDateTime expiredAt;

// 대여상태
@Column(nullable = false)
Expand All @@ -52,10 +56,15 @@ public class CarReservation extends BaseEntity {

@Builder
public CarReservation(
Member member, CarStock carStock, LocalDateTime reservedAt, ReservationStatus status) {
Member member,
CarStock carStock,
LocalDateTime startedAt,
LocalDateTime expiredAt,
ReservationStatus status) {
this.member = member;
this.carStock = carStock;
this.reservedAt = reservedAt;
this.startedAt = startedAt;
this.expiredAt = expiredAt;
this.status = status;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.testcar.car.domains.carReservation;
package com.testcar.car.domains.carReservation.entity;


import lombok.Getter;
Expand All @@ -7,10 +7,8 @@
@Getter
@RequiredArgsConstructor
public enum ReservationStatus {
AVAILABLE("대여 가능"),
INSPECTION("검수중"),
RESERVED("대여중"),
UNAVAILABLE("폐기");
RETURNED("반납완료");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.testcar.car.domains.carReservation.exception;


import com.testcar.car.common.exception.BaseErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ErrorCode implements BaseErrorCode {
CAR_STOCK_NOT_AVAILABLE("CRS001", "해당 재고는 대여 불가합니다.");

private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.testcar.car.domains.carReservation.model;


import com.testcar.car.common.annotation.DateFormat;
import com.testcar.car.domains.carReservation.entity.CarReservation;
import com.testcar.car.domains.carReservation.entity.ReservationStatus;
import com.testcar.car.domains.carReservation.model.dto.CarReservationDto;
import com.testcar.car.domains.carStock.entity.CarStock;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CarReservationResponse {
@Schema(description = "차량 예약 ID", example = "1")
private final Long id;

@Schema(description = "차량명", example = "아반떼")
private final String name;

@Schema(description = "차량 재고번호", example = "2023010300001")
private final String stockNumber;

@DateFormat
@Schema(description = "차량 대여 시작일", example = "2021-01-01")
private final LocalDateTime startedAt;

@DateFormat
@Schema(description = "차량 대여 만료일", example = "2021-01-08")
private final LocalDateTime expiredAt;

@Schema(description = "대여 상태", example = "RESERVED", implementation = ReservationStatus.class)
private final ReservationStatus status;

public static CarReservationResponse from(CarReservationDto carReservationDto) {
return CarReservationResponse.builder()
.id(carReservationDto.getId())
.name(carReservationDto.getCarName())
.stockNumber(carReservationDto.getStockNumber())
.startedAt(carReservationDto.getStartedAt())
.expiredAt(carReservationDto.getExpiredAt())
.status(carReservationDto.getStatus())
.build();
}

public static CarReservationResponse from(CarReservation carReservation) {
final CarStock carStock = carReservation.getCarStock();

return CarReservationResponse.builder()
.id(carReservation.getId())
.name(carStock.getCar().getName())
.stockNumber(carStock.getStockNumber())
.startedAt(carReservation.getStartedAt())
.expiredAt(carReservation.getExpiredAt())
.status(carReservation.getStatus())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.testcar.car.domains.carReservation.model.dto;


import com.querydsl.core.annotations.QueryProjection;
import com.testcar.car.domains.carReservation.entity.CarReservation;
import com.testcar.car.domains.carReservation.entity.ReservationStatus;
import java.time.LocalDateTime;
import lombok.Getter;

@Getter
public class CarReservationDto {
private final Long id;
private final String carName;
private final String stockNumber;
private final LocalDateTime startedAt;
private final LocalDateTime expiredAt;
private final ReservationStatus status;

@QueryProjection
public CarReservationDto(CarReservation carReservation, String carName, String stockNumber) {
this.id = carReservation.getId();
this.carName = carName;
this.stockNumber = stockNumber;
this.startedAt = carReservation.getStartedAt();
this.expiredAt = carReservation.getExpiredAt();
this.status = carReservation.getStatus();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.testcar.car.domains.carReservation.model.vo;


import com.testcar.car.common.annotation.DateFormat;
import com.testcar.car.domains.carReservation.entity.ReservationStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CarReservationFilterCondition {
@Schema(description = "차량명", example = "null")
private String name;

@Schema(description = "대여 상태", example = "null", implementation = ReservationStatus.class)
private ReservationStatus status;

@DateFormat
@Schema(description = "대여일자 시작일", example = "null")
private LocalDate startDate;

@DateFormat
@Schema(description = "대여일자 종료일", example = "null")
private LocalDate endDate;
}
Loading

0 comments on commit 09a6a21

Please sign in to comment.