Skip to content

Commit

Permalink
[FU-111] 촬영 예약신청서 등록 API 구현 (#16)
Browse files Browse the repository at this point in the history
* FU-111 feat: Json Converting을 해줄 의존성 추가

* FU-111 feat: ReservationForm Entity 추가

* FU-111 feat: ReservationStatus Enum 추가

* FU-111 feat: ReservationFormRequest DTO 추가

* FU-111 feat: 예약신청서 등록 서비스 로직 기능 추가

* FU-111 feat: 예약신청서 등록 Controller API 추가

* FU-111 refactor: 촬영일정 후보의 타입을 Map<Integer,LocalDateTime>으로 변경

* FU-111 feat: Inactive 상태인 상품을 등록하려 했을 때 필요한 ProductErrorCode 추가

* FU-111 feat: 상품 제목으로 Product를 조회하는 쿼리메서드 추가

* FU-111 feat: Inactive 상태인 상품 등록을 검증하는 기능 추가
  • Loading branch information
yuseok0215 authored Aug 9, 2024
1 parent 95c829c commit a0c7bb9
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 13 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.3.1'
implementation 'com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ApiResponseDto<T> {
public class ApiResponse<T> {
private int status;
private String message;
private T data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
@Getter
@RequiredArgsConstructor
public enum ProductErrorCode implements ErrorCode {
INVALID_ACTIVE_STATUS(400, "ActiveStatus must be different current stored ActiveStatus");
INVALID_ACTIVE_STATUS(400, "ActiveStatus must be different current stored ActiveStatus"),
PRODUCT_INACTIVE_STATUS(400, "The product is currently inactive, so you can't register a booking form with it");

private final int httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.foru.freebe.common.dto.ApiResponseDto;
import com.foru.freebe.common.dto.ApiResponse;
import com.foru.freebe.product.dto.ProductRegisterRequestDto;
import com.foru.freebe.product.dto.RegisteredProductResponseDto;
import com.foru.freebe.product.dto.UpdateProductRequestDto;
Expand All @@ -25,18 +25,18 @@ public class ProductController {
private final ProductService productService;

@PostMapping("/")
public ApiResponseDto<Void> registerProduct(@RequestBody ProductRegisterRequestDto productRegisterRequestDto) {
public ApiResponse<Void> registerProduct(@RequestBody ProductRegisterRequestDto productRegisterRequestDto) {
return productService.registerProduct(productRegisterRequestDto);
}

@GetMapping("/registered-product/{id}")
public ApiResponseDto<List<RegisteredProductResponseDto>> getRegisteredProductList(
public ApiResponse<List<RegisteredProductResponseDto>> getRegisteredProductList(
@PathVariable("id") Long memberId) {
return productService.getRegisteredProductList(memberId);
}

@PutMapping("/update-status")
public ApiResponseDto<Void> updateProductActiveStatus(@RequestBody UpdateProductRequestDto requestDto) {
public ApiResponse<Void> updateProductActiveStatus(@RequestBody UpdateProductRequestDto requestDto) {
return productService.updateProductActiveStatus(requestDto);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@

public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<List<Product>> findByMember(Member member);

Boolean existsByTitle(String title);

Product findByTitle(String title);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import org.springframework.stereotype.Service;

import com.foru.freebe.common.dto.ApiResponseDto;
import com.foru.freebe.common.dto.ApiResponse;
import com.foru.freebe.errors.errorcode.CommonErrorCode;
import com.foru.freebe.errors.exception.RestApiException;
import com.foru.freebe.member.entity.Member;
Expand Down Expand Up @@ -40,7 +40,7 @@ public class ProductService {
private final ProductDiscountRepository productDiscountRepository;
private final MemberRepository memberRepository;

public ApiResponseDto<Void> registerProduct(ProductRegisterRequestDto productRegisterRequestDto) {
public ApiResponse<Void> registerProduct(ProductRegisterRequestDto productRegisterRequestDto) {
Member member = getMember(productRegisterRequestDto.getMemberId());

String productTitle = productRegisterRequestDto.getProductTitle();
Expand Down Expand Up @@ -69,14 +69,14 @@ public ApiResponseDto<Void> registerProduct(ProductRegisterRequestDto productReg
registerDiscount(productRegisterRequestDto.getProductDiscounts(), productAsActive);
}

return ApiResponseDto.<Void>builder()
return ApiResponse.<Void>builder()
.status(200)
.message("Successfully added")
.data(null)
.build();
}

public ApiResponseDto<List<RegisteredProductResponseDto>> getRegisteredProductList(Long memberId) {
public ApiResponse<List<RegisteredProductResponseDto>> getRegisteredProductList(Long memberId) {
Member member = getMember(memberId);
List<Product> registeredProductList = productRepository.findByMember(member)
.orElseThrow(() -> new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND));
Expand All @@ -90,20 +90,20 @@ public ApiResponseDto<List<RegisteredProductResponseDto>> getRegisteredProductLi
.build())
.collect(Collectors.toList());

return ApiResponseDto.<List<RegisteredProductResponseDto>>builder()
return ApiResponse.<List<RegisteredProductResponseDto>>builder()
.status(200)
.message("Successfully retrieved list of registered products")
.data(registeredProducts)
.build();
}

@Transactional
public ApiResponseDto<Void> updateProductActiveStatus(UpdateProductRequestDto requestDto) {
public ApiResponse<Void> updateProductActiveStatus(UpdateProductRequestDto requestDto) {
Product product = productRepository.findById(requestDto.getProductId())
.orElseThrow(() -> new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND));
product.updateProductActiveStatus(requestDto.getActiveStatus());

return ApiResponseDto.<Void>builder()
return ApiResponse.<Void>builder()
.status(200)
.message("Successfully updated product active status")
.data(null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.foru.freebe.reservation.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.foru.freebe.common.dto.ApiResponse;
import com.foru.freebe.reservation.dto.ReservationFormRequest;
import com.foru.freebe.reservation.service.ReservationFormService;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/reservation")
public class ReservationFormController {
private final ReservationFormService reservationFormService;

@PostMapping("/")
public ApiResponse<Void> registerReservationForm(@RequestBody ReservationFormRequest reservationFormRequest) {
return reservationFormService.registerReservationForm(reservationFormRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.foru.freebe.reservation.dto;

import java.time.LocalDateTime;
import java.util.Map;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ReservationFormRequest {
private Long photographerId;
private Long customerId; // TODO 추후 토큰 로직으로 대체
@NotNull
private String instagramId;
@NotNull
private String productTitle;
private Map<String, String> photoInfo;
private Map<Integer, LocalDateTime> photoSchedule;
private String requestMemo;
@NotNull
private Long totalPrice;
@NotNull
private Boolean serviceTermAgreement;
@NotNull
private Boolean photographerTermAgreement;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.foru.freebe.reservation.entity;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.annotations.Type;

import com.foru.freebe.member.entity.Member;

import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReservationForm {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "reservation_form_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "photographer_id")
private Member photographer;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Member customer;

@NotNull
private String instagramId;

@NotNull
private String productTitle;

@Type(JsonType.class)
@Column(name = "photo_info", columnDefinition = "longtext")
private Map<String, String> photoInfo = new HashMap<>();

@Type(JsonType.class)
@Column(name = "photo_schedule", columnDefinition = "longtext")
private Map<Integer, LocalDateTime> photoSchedule = new HashMap<>();

private String requestMemo;

private String photographerMemo;

@NotNull
private Long totalPrice;

@NotNull
private Boolean serviceTermAgreement;

@NotNull
private Boolean photographerTermAgreement;

@NotNull
private ReservationStatus reservationStatus;

@Builder
public ReservationForm(Member photographer, Member customer, String instagramId, String productTitle,
Map<String, String> photoInfo, Map<Integer, LocalDateTime> photoSchedule, String requestMemo,
String photographerMemo, Long totalPrice, Boolean serviceTermAgreement, Boolean photographerTermAgreement,
ReservationStatus reservationStatus) {
this.photographer = photographer;
this.customer = customer;
this.instagramId = instagramId;
this.productTitle = productTitle;
this.photoInfo = photoInfo;
this.photoSchedule = photoSchedule;
this.requestMemo = requestMemo;
this.photographerMemo = photographerMemo;
this.totalPrice = totalPrice;
this.serviceTermAgreement = serviceTermAgreement;
this.photographerTermAgreement = photographerTermAgreement;
this.reservationStatus = reservationStatus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.foru.freebe.reservation.entity;

public enum ReservationStatus {
NEW,
IN_PROGRESS,
WAITING_FOR_DEPOSIT,
WAITING_FOR_PHOTO
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.foru.freebe.reservation.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.foru.freebe.reservation.entity.ReservationForm;

public interface ReservationFormRepository extends JpaRepository<ReservationForm, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.foru.freebe.reservation.service;

import org.springframework.stereotype.Service;

import com.foru.freebe.common.dto.ApiResponse;
import com.foru.freebe.errors.errorcode.CommonErrorCode;
import com.foru.freebe.errors.errorcode.ProductErrorCode;
import com.foru.freebe.errors.exception.RestApiException;
import com.foru.freebe.member.entity.Member;
import com.foru.freebe.member.repository.MemberRepository;
import com.foru.freebe.product.entity.ActiveStatus;
import com.foru.freebe.product.entity.Product;
import com.foru.freebe.product.respository.ProductRepository;
import com.foru.freebe.reservation.dto.ReservationFormRequest;
import com.foru.freebe.reservation.entity.ReservationForm;
import com.foru.freebe.reservation.entity.ReservationStatus;
import com.foru.freebe.reservation.repository.ReservationFormRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ReservationFormService {
private final ReservationFormRepository reservationFormRepository;
private final MemberRepository memberRepository;
private final ProductRepository productRepository;

public ApiResponse<Void> registerReservationForm(ReservationFormRequest reservationFormRequest) {
Member customer = memberRepository.findById(reservationFormRequest.getCustomerId())
.orElseThrow(() -> new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND));

Member photographer = memberRepository.findById(reservationFormRequest.getPhotographerId())
.orElseThrow(() -> new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND));

validateProductTitleExists(reservationFormRequest.getProductTitle());

ReservationForm reservationForm = ReservationForm.builder()
.photographer(photographer)
.customer(customer)
.instagramId(reservationFormRequest.getInstagramId())
.productTitle(reservationFormRequest.getProductTitle())
.photoInfo(reservationFormRequest.getPhotoInfo())
.photoSchedule(reservationFormRequest.getPhotoSchedule())
.requestMemo(reservationFormRequest.getRequestMemo())
.totalPrice(reservationFormRequest.getTotalPrice())
.serviceTermAgreement(reservationFormRequest.getServiceTermAgreement())
.photographerTermAgreement(reservationFormRequest.getPhotographerTermAgreement())
.reservationStatus(ReservationStatus.NEW)
.build();

validateActiveStatusOfProduct(reservationFormRequest, reservationForm);
reservationFormRepository.save(reservationForm);

return ApiResponse.<Void>builder()
.status(200)
.message("Good Request")
.data(null)
.build();
}

private void validateActiveStatusOfProduct(ReservationFormRequest reservationFormRequest,
ReservationForm reservationForm) {
Product product = productRepository.findByTitle(reservationFormRequest.getProductTitle());
if (product.getActiveStatus() != ActiveStatus.ACTIVE) {
throw new RestApiException(ProductErrorCode.PRODUCT_INACTIVE_STATUS);
}
}

private void validateProductTitleExists(String productTitle) {
if (!productRepository.existsByTitle(productTitle)) {
throw new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND);
}
}
}

0 comments on commit a0c7bb9

Please sign in to comment.