Skip to content

Commit

Permalink
[feat] 가게의 음식 상품 가격 수정 (#107)
Browse files Browse the repository at this point in the history
* [feat] update할 음식의 가격이 0 또는 음수일 경우 던져질 Exception 구현

* [feat] Menu 도메인 엔티티에서 update할 price를 받으면 자신의 price를 업데이트 하는 메서드 구현

- 음수, 0 이라면 InvalidMenuPriceUpdateException 던짐

- Dynamic Update를 이용해 수정된 가격만 업데이트하는 쿼리를 보내기 위해 사용

* [test] Menu 도메인 엔티티에서 update할 price를 받으면 자신의 price를 업데이트 하는 메서드 테스트

- 음수, 혹은 양수면 InvalidMenuPriceUpdateException 발생 테스트
- 양수라면 업데이트 된 가격으로 업데이트 됐는지 확인

* [feat] Menu의 가격을 Update하기 위한 Command 구현

- vendorId : 업데이트를 시도하는 vendor의 id
- menuId : 업데이트 할 menu의 id
- updatePrice : 업데이트 할 가격

* [feat] Menu의 가격을 Update할 수 있는 MenuPriceUpdateService 구현

- 존재하는 메뉴만 업데이트 할 수 있다.
- 자신의 가게만 업데이트 할 수 있다.
- 가격은 양수만 가능하다.

* [feat] 더미용 Menu를 생성하기 위한 createMenu 메서드 구현

- 테스트용 Menu를 매번 생성하면 중복코드가 많이 생기기 때문에 더미 객체 생성용 메서드 생성

* [test] Menu의 가격을 Update할 수 있는 MenuPriceUpdateService 테스트 구현

- 존재하는 메뉴만 업데이트 할 수 있다.
- 자신의 가게만 업데이트 할 수 있다.
- 가격은 양수만 가능하다.

* [fix] 메뉴의 주인이 아닌 점주가 수정 요청을 했을 때, 400을 던지는 로직에서 403을 던지도록 수정

* [feat] 클라이언트에게 주고 받을 DTO 생성

- 업데이트 할 price를 받고 업데이트 된 price를 준다.

* [feat] 업데이트 할 엔드포인트 구현 및 exception handler에 추가

* [test] StoreApiController에 추가한 update price관련 로직 테스트 추가

- 업데이트 가격이 0일경우 400
- 업데이트 가격이 음수일경우 400
- 벤더가 아니면 401
- 내 메뉴를 수정하는것이 아니면 403

* [fix] MenuPriceUpdateService 에서 자신의 가게의 메뉴를 수정할 때 던져지는 Exception을 MenuOwnerNotMatchException 로 수정
  • Loading branch information
Hyeon-Uk authored Aug 18, 2024
1 parent 0dced88 commit 48bcf4b
Show file tree
Hide file tree
Showing 14 changed files with 455 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/main/java/camp/woowak/lab/menu/domain/Menu.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package camp.woowak.lab.menu.domain;

import org.hibernate.annotations.DynamicUpdate;

import camp.woowak.lab.menu.exception.InvalidMenuPriceUpdateException;
import camp.woowak.lab.menu.exception.NotEnoughStockException;
import camp.woowak.lab.store.domain.Store;
import jakarta.persistence.Column;
Expand All @@ -17,6 +20,7 @@
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@DynamicUpdate
public class Menu {

@Id
Expand Down Expand Up @@ -65,4 +69,13 @@ public void decrementStockCount(int amount) {
}
stockCount -= amount;
}

public long updatePrice(long uPrice) {
if (uPrice <= 0) {
throw new InvalidMenuPriceUpdateException("메뉴의 가격은 0원보다 커야합니다. 입력값 : " + uPrice);
}
this.price = uPrice;

return this.price;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package camp.woowak.lab.menu.exception;

import camp.woowak.lab.common.exception.BadRequestException;

public class InvalidMenuPriceUpdateException extends BadRequestException {
public InvalidMenuPriceUpdateException(String message) {
super(MenuErrorCode.INVALID_PRICE, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public enum MenuErrorCode implements ErrorCode {

NOT_FOUND_MENU(HttpStatus.BAD_REQUEST, "m_8", "메뉴를 찾을 수 없습니다."),
NOT_FOUND_MENU_CATEGORY(HttpStatus.BAD_REQUEST, "m_9", "메뉴 카테고리를 찾을 수 없습니다."),

MENU_OWNER_NOT_MATCH(HttpStatus.FORBIDDEN, "m_10", "메뉴는 가게의 점주만 수정할 수 있습니다."),

NOT_ENOUGH_STOCK(HttpStatus.BAD_REQUEST, "M4", "재고가 부족합니다.");

private final int status;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package camp.woowak.lab.menu.exception;

import camp.woowak.lab.common.exception.ForbiddenException;

public class MenuOwnerNotMatchException extends ForbiddenException {
public MenuOwnerNotMatchException(String message) {
super(MenuErrorCode.MENU_OWNER_NOT_MATCH, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package camp.woowak.lab.menu.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import camp.woowak.lab.menu.domain.Menu;
import camp.woowak.lab.menu.exception.MenuOwnerNotMatchException;
import camp.woowak.lab.menu.exception.UnauthorizedMenuCategoryCreationException;
import camp.woowak.lab.menu.repository.MenuRepository;
import camp.woowak.lab.menu.service.command.MenuPriceUpdateCommand;
import camp.woowak.lab.order.exception.NotFoundMenuException;
import camp.woowak.lab.store.domain.Store;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class MenuPriceUpdateService {
private final MenuRepository menuRepository;

public MenuPriceUpdateService(MenuRepository menuRepository) {
this.menuRepository = menuRepository;
}

/**
* @throws camp.woowak.lab.menu.exception.InvalidMenuPriceUpdateException 업데이트 하려는 가격이 0 또는 음수인 경우
* @throws UnauthorizedMenuCategoryCreationException 자신의 가게의 메뉴가 아닌 메뉴를 업데이트 하려는 경우
* @throws NotFoundMenuException 존재하지 않는 메뉴의 가격을 업데이트 하려는 경우
*/
@Transactional
public long updateMenuPrice(MenuPriceUpdateCommand cmd) {
Menu menu = menuRepository.findByIdWithStore(cmd.menuId())
.orElseThrow(() -> {
log.info("등록되지 않은 메뉴 {}의 가격 수정을 시도했습니다.", cmd.menuId());
throw new NotFoundMenuException("등록되지 않은 Menu의 가격 수정을 시도했습니다.");
});

Store store = menu.getStore();
if (!store.isOwnedBy(cmd.vendorId())) {
log.info("권한없는 사용자 {}가 점포 {}의 메뉴 가격 수정을 시도했습니다.", cmd.vendorId(), store.getId());
throw new MenuOwnerNotMatchException("권한없는 사용자가 메뉴 가격 수정을 시도했습니다.");
}

long updatedPrice = menu.updatePrice(cmd.updatePrice());
log.info("Store({}) 의 메뉴({}) 가격을 ({})로 수정했습니다.", store.getId(), menu.getId(), cmd.updatePrice());

return updatedPrice;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package camp.woowak.lab.menu.service.command;

import java.util.UUID;

/**
* @param vendorId 업데이트를 시도하는 vendor의 id
* @param menuId 업데이트 할 menu의 id
* @param updatePrice 업데이트 할 가격
*/
public record MenuPriceUpdateCommand(
UUID vendorId,
Long menuId,
Long updatePrice
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import camp.woowak.lab.menu.service.MenuCategoryRegistrationService;
import camp.woowak.lab.menu.service.MenuPriceUpdateService;
import camp.woowak.lab.menu.service.command.MenuCategoryRegistrationCommand;
import camp.woowak.lab.menu.service.command.MenuPriceUpdateCommand;
import camp.woowak.lab.store.service.StoreMenuRegistrationService;
import camp.woowak.lab.store.service.StoreRegistrationService;
import camp.woowak.lab.store.service.command.StoreMenuRegistrationCommand;
Expand All @@ -19,10 +22,12 @@
import camp.woowak.lab.web.authentication.annotation.AuthenticationPrincipal;
import camp.woowak.lab.web.dao.store.StoreDao;
import camp.woowak.lab.web.dto.request.store.MenuCategoryRegistrationRequest;
import camp.woowak.lab.web.dto.request.store.MenuPriceUpdateRequest;
import camp.woowak.lab.web.dto.request.store.StoreInfoListRequest;
import camp.woowak.lab.web.dto.request.store.StoreMenuRegistrationRequest;
import camp.woowak.lab.web.dto.request.store.StoreRegistrationRequest;
import camp.woowak.lab.web.dto.response.store.MenuCategoryRegistrationResponse;
import camp.woowak.lab.web.dto.response.store.MenuPriceUpdateResponse;
import camp.woowak.lab.web.dto.response.store.StoreInfoListResponse;
import camp.woowak.lab.web.dto.response.store.StoreMenuRegistrationResponse;
import camp.woowak.lab.web.dto.response.store.StoreRegistrationResponse;
Expand All @@ -36,6 +41,7 @@ public class StoreApiController {
private final StoreRegistrationService storeRegistrationService;
private final StoreMenuRegistrationService storeMenuRegistrationService;
private final MenuCategoryRegistrationService menuCategoryRegistrationService;
private final MenuPriceUpdateService menuPriceUpdateService;
private final StoreDao storeDao;

@GetMapping("/stores")
Expand Down Expand Up @@ -84,6 +90,17 @@ public StoreMenuRegistrationResponse storeMenuRegistration(final @Authentication
return new StoreMenuRegistrationResponse(menuIds);
}

@PatchMapping("/stores/menus/{menuId}/price")
public MenuPriceUpdateResponse menuPriceUpdate(final @AuthenticationPrincipal LoginVendor loginVendor,
final @PathVariable Long menuId,
final @Valid @RequestBody MenuPriceUpdateRequest request
) {
MenuPriceUpdateCommand command = new MenuPriceUpdateCommand(loginVendor.getId(), menuId, request.price());

long updatedPrice = menuPriceUpdateService.updateMenuPrice(command);
return new MenuPriceUpdateResponse(updatedPrice);
}

@PostMapping("/stores/{storeId}/category")
public MenuCategoryRegistrationResponse storeCategoryRegistration(@AuthenticationPrincipal LoginVendor loginVendor,
@PathVariable Long storeId,
Expand All @@ -93,4 +110,5 @@ public MenuCategoryRegistrationResponse storeCategoryRegistration(@Authenticatio
Long registeredId = menuCategoryRegistrationService.register(command);
return new MenuCategoryRegistrationResponse(registeredId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import camp.woowak.lab.common.exception.HttpStatusException;
import camp.woowak.lab.menu.exception.InvalidMenuCategoryCreationException;
import camp.woowak.lab.menu.exception.InvalidMenuCreationException;
import camp.woowak.lab.menu.exception.MenuOwnerNotMatchException;
import camp.woowak.lab.menu.exception.NotFoundMenuCategoryException;
import camp.woowak.lab.store.exception.InvalidStoreCreationException;
import camp.woowak.lab.store.exception.NotEqualsOwnerException;
Expand Down Expand Up @@ -39,6 +40,15 @@ public ResponseEntity<ProblemDetail> handleException(NotEqualsOwnerException exc
return ResponseEntity.status(badRequest).body(problemDetail);
}

@ExceptionHandler(MenuOwnerNotMatchException.class)
public ProblemDetail handleException(MenuOwnerNotMatchException exception) {
log.warn("Forbidden",exception);
HttpStatus forbidden = HttpStatus.FORBIDDEN;
ProblemDetail problemDetail = getProblemDetail(exception, forbidden);

return problemDetail;
}

@ExceptionHandler(NotFoundStoreCategoryException.class)
public ResponseEntity<ProblemDetail> handleException(NotFoundStoreCategoryException exception) {
log.warn("Not Found", exception);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package camp.woowak.lab.web.dto.request.store;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record MenuPriceUpdateRequest(
@Min(value = 1, message = "price값은 음수 혹은 0이 될 수 없습니다.")
@NotNull(message = "price값은 필수 입니다.")
Long price
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package camp.woowak.lab.web.dto.response.store;

public record MenuPriceUpdateResponse(
long updatedPrice
) {
}
6 changes: 6 additions & 0 deletions src/test/java/camp/woowak/lab/fixture/MenuFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import java.time.LocalDateTime;
import java.util.UUID;

import camp.woowak.lab.menu.TestMenu;
import camp.woowak.lab.menu.TestMenuCategory;
import camp.woowak.lab.menu.domain.Menu;
import camp.woowak.lab.menu.domain.MenuCategory;
import camp.woowak.lab.payaccount.domain.PayAccount;
import camp.woowak.lab.payaccount.domain.TestPayAccount;
Expand Down Expand Up @@ -57,4 +59,8 @@ default MenuCategory createMenuCategory(Long id, Store store, String name) {
default StoreCategory createStoreCategory() {
return new StoreCategory("양식");
}

default Menu createMenu(Long id, Store store, MenuCategory menuCategory, String name, long price) {
return new TestMenu(id, store, menuCategory, name, price);
}
}
53 changes: 53 additions & 0 deletions src/test/java/camp/woowak/lab/menu/domain/MenuTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import org.junit.jupiter.api.Test;

import camp.woowak.lab.common.exception.ErrorCode;
import camp.woowak.lab.common.exception.HttpStatusException;
import camp.woowak.lab.menu.exception.InvalidMenuCreationException;
import camp.woowak.lab.menu.exception.InvalidMenuPriceUpdateException;
import camp.woowak.lab.menu.exception.MenuErrorCode;
import camp.woowak.lab.payaccount.domain.PayAccount;
import camp.woowak.lab.payaccount.domain.TestPayAccount;
Expand Down Expand Up @@ -291,6 +293,57 @@ void isBlank() {

}

@Nested
@DisplayName("메뉴 가격 업데이트는")
class UpdatePriceTest {
private long menuPrice = 10000;
private Menu menu = new Menu(storeFixture, menuCategoryFixture, "메뉴1", menuPrice, 10L, "imageUrl");

@Nested
@DisplayName("업데이트 하려는 가격이")
class UpdatePrice {
@Test
@DisplayName("[Exception] 음수이면 InvalidMenuPriceUpdateException이 발생한다.")
void negativeUpdatePrice() {
//given
long updatePrice = -1;

//when & then
HttpStatusException throwable = (HttpStatusException)catchThrowable(
() -> menu.updatePrice(updatePrice));
assertThat(throwable).isInstanceOf(InvalidMenuPriceUpdateException.class);
assertThat(throwable.errorCode()).isEqualTo(MenuErrorCode.INVALID_PRICE);
}

@Test
@DisplayName("[Exception] 0이면 InvalidMenuPriceUpdateException이 발생한다.")
void zeroUpdatePrice() {
//given
long updatePrice = 0;

//when & then
HttpStatusException throwable = (HttpStatusException)catchThrowable(
() -> menu.updatePrice(updatePrice));
assertThat(throwable).isInstanceOf(InvalidMenuPriceUpdateException.class);
assertThat(throwable.errorCode()).isEqualTo(MenuErrorCode.INVALID_PRICE);
}

@Test
@DisplayName("[success] 양수면 가격이 update되고 update된 가격이 return된다.")
void positiveUpdatePrice() {
//given
long updatePrice = 9000;

//when
long updatedPrice = menu.updatePrice(updatePrice);

//then
assertThat(updatedPrice).isEqualTo(updatePrice);
assertThat(menu.getPrice()).isEqualTo(updatePrice);
}
}
}

private void assertExceptionAndErrorCode(Throwable thrown, ErrorCode expected) {
assertThat(thrown).isInstanceOf(InvalidMenuCreationException.class);
InvalidMenuCreationException exception = (InvalidMenuCreationException)thrown;
Expand Down
Loading

0 comments on commit 48bcf4b

Please sign in to comment.