Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.nowait.applicationadmin.menu.controller;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -14,6 +16,7 @@

import com.nowait.applicationadmin.menu.dto.MenuCreateRequest;
import com.nowait.applicationadmin.menu.dto.MenuCreateResponse;
import com.nowait.applicationadmin.menu.dto.MenuSortUpdateRequest;
import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest;
import com.nowait.applicationadmin.menu.service.MenuService;
import com.nowait.common.api.ApiUtils;
Expand Down Expand Up @@ -125,4 +128,20 @@ public ResponseEntity<?> toggleSoldOut(@PathVariable Long menuId) {
)
);
}

@PatchMapping("/update-sort")
@Operation(summary = "메뉴 순서 수정", description = "메뉴의 순서를 수정합니다.")
@ApiResponse(responseCode = "200", description = "메뉴 순서 수정")
public ResponseEntity<?> updateMenuSortOrder(
@Valid @RequestBody List<MenuSortUpdateRequest> request,
@AuthenticationPrincipal MemberDetails memberDetails
) {
return ResponseEntity
.status(HttpStatus.OK)
.body(
ApiUtils.success(
menuService.updateMenuSortOrder(request, memberDetails)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public Menu toEntity() {
.description(description)
.price(price)
.isSoldOut(false)
.sortOrder(0L)
.deleted(false)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import com.nowait.domaincorerdb.menu.entity.Menu;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -20,6 +19,7 @@ public class MenuCreateResponse {
private String description;
private Integer price;
private Boolean isSoldOut;
private Long sortOrder;
private Boolean deleted;
private LocalDateTime createdAt;

Expand All @@ -34,6 +34,7 @@ public static MenuCreateResponse fromEntity(Menu menu) {
.description(menu.getDescription())
.price(menu.getPrice())
.isSoldOut(menu.getIsSoldOut())
.sortOrder(menu.getSortOrder())
.deleted(menu.getDeleted())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import com.nowait.domaincorerdb.menu.entity.Menu;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -19,6 +18,7 @@ public class MenuReadDto {
private String name;
private String description;
private Integer price;
private Long sortOrder;
private Boolean isSoldOut;
private Boolean deleted;
private List<MenuImageUploadResponse> images;
Expand All @@ -31,6 +31,7 @@ public static MenuReadDto fromEntity(Menu menu, List<MenuImageUploadResponse> im
.name(menu.getName())
.description(menu.getDescription())
.price(menu.getPrice())
.sortOrder(menu.getSortOrder())
.isSoldOut(menu.getIsSoldOut())
.deleted(menu.getDeleted())
.images(images)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nowait.applicationadmin.menu.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MenuSortUpdateRequest {
@NotNull
private Long menuId;
@NotNull
@PositiveOrZero
private Long sortOrder;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nowait.applicationadmin.menu.service;

import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -10,6 +11,7 @@
import com.nowait.applicationadmin.menu.dto.MenuImageUploadResponse;
import com.nowait.applicationadmin.menu.dto.MenuReadDto;
import com.nowait.applicationadmin.menu.dto.MenuReadResponse;
import com.nowait.applicationadmin.menu.dto.MenuSortUpdateRequest;
import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest;
import com.nowait.common.enums.Role;
import com.nowait.domaincorerdb.menu.entity.Menu;
Expand Down Expand Up @@ -60,7 +62,7 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberD

// 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인
validateMenuViewAuthorization(user, storeId);
List<Menu> menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId);
List<Menu> menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId);

List<MenuReadDto> menuReadResponse = menus.stream()
.map(menu -> {
Expand Down Expand Up @@ -123,6 +125,48 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDeta
return MenuReadDto.fromEntity(saved, imageDto);
}

@Transactional
public String updateMenuSortOrder(List<MenuSortUpdateRequest> requests, MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);

if (!Role.SUPER_ADMIN.equals(user.getRole())) {
throw new MenuUpdateUnauthorizedException();
}

if (requests == null || requests.isEmpty()) {
throw new MenuParamEmptyException();
}

if (requests.stream().map(MenuSortUpdateRequest::getMenuId).distinct().count() != requests.size()) {
throw new IllegalArgumentException("중복된 메뉴 ID가 포함되어 있습니다.");
}

if (requests.stream().anyMatch(r -> r.getSortOrder() == null || r.getSortOrder() < 0)) {
throw new IllegalArgumentException("잘못된 정렬 순서가 포함되어 있습니다. 정렬 순서는 0 이상의 정수여야 합니다.");
}

List<Long> ids = requests.stream().map(MenuSortUpdateRequest::getMenuId).toList();
List<Menu> menus = menuRepository.findAllById(ids);
if (menus.size() != ids.size()) {
throw new MenuNotFoundException();
}

Long storeId = menus.get(0).getStoreId();
if (!menus.stream().allMatch(m -> storeId.equals(m.getStoreId()))) {
throw new IllegalArgumentException("다른 매장의 메뉴가 있습니다.");
Comment on lines +149 to +156
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

소프트 삭제 메뉴까지 업데이트될 수 있는 위험

findAllById(ids)는 soft-delete 여부를 고려하지 않아 삭제된 메뉴의 sortOrder가 갱신될 수 있습니다. 기존 서비스 전반이 ...DeletedFalse 패턴을 사용하므로 여기에서도 동일하게 제한해야 합니다.

권장 수정:

  • Repository에 findAllByIdInAndDeletedFalse(Collection<Long> ids)(또는 findAllByIdInAndDeletedFalseOrderById)를 추가하고 아래처럼 호출부를 변경하세요.
- List<Menu> menus = menuRepository.findAllById(ids);
+ List<Menu> menus = menuRepository.findAllByIdInAndDeletedFalse(ids);
  if (menus.size() != ids.size()) {
    throw new MenuNotFoundException();
  }

대안:

  • 당장 Repository 확장이 어렵다면, 조회 후 소프트 삭제된 엔티티를 필터링하고 발견 시 예외를 던지도록 방어 로직을 추가하세요(엔티티의 deleted 플래그 접근자에 맞춰 구현).

}

Map<Long, Long> idToSort = requests.stream()
.collect(java.util.stream.Collectors.toMap(MenuSortUpdateRequest::getMenuId,
MenuSortUpdateRequest::getSortOrder));

menus.forEach(m -> m.updateSortOrder(idToSort.get(m.getId())));

menuRepository.saveAll(menus);

return "메뉴 순서가 성공적으로 업데이트되었습니다.";
}

@Transactional
public String deleteMenu(Long menuId, MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nowait.applicationadmin.order.dto;

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

Expand All @@ -19,14 +20,18 @@ public class OrderResponseDto {
private String depositorName;
private Integer totalPrice;
private OrderStatus status;
private Map<String, MenuDetail> menuDetails;
private HashMap<String, MenuDetail> menuDetails;
private LocalDateTime createdAt;

public static OrderResponseDto fromEntity(UserOrder userOrder) {
Map<String, MenuDetail> menuDetails = new LinkedHashMap<>();
HashMap<String, MenuDetail> menuDetails = new LinkedHashMap<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

선언-구현 타입 불일치 — 목적(순서 보존)에 맞게 Map 또는 LinkedHashMap으로 선언 정리

변수 타입은 HashMap인데 실제 인스턴스는 LinkedHashMap입니다. 읽기 혼란을 줄이고 의도를 드러내기 위해 인터페이스(Map)로 선언하거나 LinkedHashMap으로 맞춰 주세요. (아래 필드 타입 변경과도 일관성을 가집니다.)

- HashMap<String, MenuDetail> menuDetails = new LinkedHashMap<>();
+ Map<String, MenuDetail> menuDetails = new LinkedHashMap<>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HashMap<String, MenuDetail> menuDetails = new LinkedHashMap<>();
Map<String, MenuDetail> menuDetails = new LinkedHashMap<>();
🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/dto/OrderResponseDto.java
at line 27, the variable menuDetails is declared as HashMap but instantiated as
LinkedHashMap, causing a type mismatch and potential confusion. To fix this,
change the declaration type to either Map or LinkedHashMap to match the
instantiation and clarify the intent of preserving order, ensuring consistency
with related field declarations.


for (OrderItem item : userOrder.getOrderItems()) {
String displayName = item.getMenu().getAdminDisplayName();
String adminDisplayName = item.getMenu().getAdminDisplayName();
String displayName = (adminDisplayName == null || adminDisplayName.isBlank())
? item.getMenu().getName() // 관리자 표시명이 없거나 공백이면 일반 메뉴명 사용
: adminDisplayName;

int quantity = item.getQuantity();
int price = item.getMenu().getPrice(); // 메뉴 단가

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class MenuReadDto {
private String name;
private String description;
private Integer price;
private Long sortOrder;
private Boolean isSoldOut;
private Boolean deleted;
private List<MenuImageUploadResponse> images;
Expand All @@ -28,6 +29,7 @@ public static MenuReadDto fromEntity(Menu menu, List<MenuImageUploadResponse> im
.name(menu.getName())
.description(menu.getDescription())
.price(menu.getPrice())
.sortOrder(menu.getSortOrder())
.isSoldOut(menu.getIsSoldOut())
.deleted(menu.getDeleted())
.images(images)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) {
if (storeId == null) {
throw new MenuParamEmptyException();
}
List<Menu> menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId);
List<Menu> menus = menuRepository.findAllByStoreIdAndDeletedFalseOrderBySortOrder(storeId);

List<MenuReadDto> menuReadResponse = menus.stream()
.map(menu -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class Menu extends BaseTimeEntity {
@Column(nullable = false)
private Boolean isSoldOut;

@Column(nullable = false)
private Long sortOrder;

@Column(nullable = false)
private Boolean deleted;

Expand All @@ -72,4 +75,8 @@ public void updateInfo(String adminDisplayName, String name, String description,
public void markAsDeleted() { this.deleted = true; }

public void toggleSoldOut() { this.isSoldOut = !this.isSoldOut; }

public void updateSortOrder(Long sortOrder) {
this.sortOrder = sortOrder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@Repository
public interface MenuRepository extends JpaRepository<Menu, Long> {
List<Menu> findAllByStoreIdAndDeletedFalse(Long storeId);
List<Menu> findAllByStoreIdAndDeletedFalseOrderBySortOrder(Long storeId);
Optional<Menu> findByStoreIdAndIdAndDeletedFalse(Long storeId, Long menuId);
Optional<Menu> findByIdAndDeletedFalse(Long menuId);
}