Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 어드민 혜택 상세 페이지 혜택문구 미리보기 추가 #1132

Merged
merged 11 commits into from
Dec 12, 2024
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
Expand Up @@ -21,6 +21,7 @@
import in.koreatech.koin.admin.benefit.dto.AdminDeleteShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryResponse;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminSearchBenefitShopsResponse;
import in.koreatech.koin.global.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -127,6 +128,22 @@ ResponseEntity<AdminCreateBenefitShopsResponse> createBenefitShops(
@Auth(permit = {ADMIN}) Integer adminId
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "201"),
@ApiResponse(responseCode = "400", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "특정 혜택을 제공하는 상점을 수정한다.")
@PutMapping
ResponseEntity<Void> modifyBenefitShops(
@RequestBody AdminModifyBenefitShopsRequest request,
@Auth(permit = {ADMIN}) Integer adminId
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "204"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import in.koreatech.koin.admin.benefit.dto.AdminDeleteShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryResponse;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminSearchBenefitShopsResponse;
import in.koreatech.koin.admin.benefit.service.AdminBenefitService;
import in.koreatech.koin.global.auth.Auth;
Expand Down Expand Up @@ -90,6 +91,15 @@ public ResponseEntity<AdminCreateBenefitShopsResponse> createBenefitShops(
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@PutMapping
public ResponseEntity<Void> modifyBenefitShops(
@RequestBody AdminModifyBenefitShopsRequest request,
@Auth(permit = {ADMIN}) Integer adminId
) {
adminBenefitService.modifyBenefitShops(request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}/shops")
public ResponseEntity<Void> deleteBenefitShops(
@PathVariable("id") Integer benefitId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.domain.benefit.model.BenefitCategoryMap;
import in.koreatech.koin.domain.shop.model.shop.Shop;
import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminBenefitShopsResponse(
@Schema(example = "3", description = "상점 개수")
Integer count,
Expand All @@ -16,27 +18,36 @@ public record AdminBenefitShopsResponse(
List<InnerShopResponse> shops
) {

public static AdminBenefitShopsResponse from(List<Shop> shops) {
public static AdminBenefitShopsResponse from(List<BenefitCategoryMap> benefitCategoryMaps) {
return new AdminBenefitShopsResponse(
shops.size(),
shops.stream()
benefitCategoryMaps.size(),
benefitCategoryMaps.stream()
.map(InnerShopResponse::from)
.toList()
);
}

@JsonNaming(SnakeCaseStrategy.class)
private record InnerShopResponse(
@Schema(example = "1", description = "상점혜택 매핑id")
Integer shopBenefitMapId,

@Schema(example = "1", description = "고유 id")
Integer id,

@Schema(example = "수신반점", description = "이름")
String name
String name,

@Schema(example = "4인 이상 픽업서비스", description = "혜택 미리보기 문구")
String detail
) {

public static InnerShopResponse from(Shop shop) {
public static InnerShopResponse from(BenefitCategoryMap benefitCategoryMap) {
return new InnerShopResponse(
shop.getId(),
shop.getName()
benefitCategoryMap.getId(),
benefitCategoryMap.getShop().getId(),
benefitCategoryMap.getShop().getName(),
benefitCategoryMap.getDetail()
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,27 @@

import in.koreatech.koin.global.validation.NotBlankElement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminCreateBenefitShopsRequest(
@Schema(description = "상점 ID 리스트", example = "[1, 2, 5]", requiredMode = REQUIRED)
@NotNull(message = "상점 ID 리스트는 필수입니다.")
@NotBlankElement(message = "상점 ID 리스트는 빈 요소가 존재할 수 없습니다.")
List<Integer> shopIds
@NotNull(message = "상점정보 리스트는 필수입니다.")
@NotBlankElement(message = "상점정보 리스트는 빈 요소가 존재할 수 없습니다.")
List<InnerBenefitShopsRequest> shopDetails
) {

@JsonNaming(SnakeCaseStrategy.class)
public record InnerBenefitShopsRequest(
@Schema(description = "상점 고유 id", example = "2", requiredMode = REQUIRED)
@NotNull(message = "상점은 필수입니다.")
Integer shopId,

@Schema(description = "혜택 미리보기 문구", example = "4인 이상 픽업서비스", requiredMode = REQUIRED)
@NotBlank(message = "혜택 미리보기 문구는 필수입니다.")
@Size(min = 2, max = 15, message = "혜택 미리보기 문구는 최소 2자 최대 15자입니다.")
String detail
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.domain.benefit.model.BenefitCategoryMap;
import in.koreatech.koin.domain.shop.model.shop.Shop;
import io.swagger.v3.oas.annotations.media.Schema;

public record AdminCreateBenefitShopsResponse(
@Schema(description = "상점 리스트")
@Schema(description = "상점 정보")
List<InnerShopResponse> shops
) {
public static AdminCreateBenefitShopsResponse from(List<Shop> shops) {
public static AdminCreateBenefitShopsResponse from(List<BenefitCategoryMap> benefitCategoryMaps) {
return new AdminCreateBenefitShopsResponse(
shops.stream().map(InnerShopResponse::from).toList()
benefitCategoryMaps.stream()
.map(InnerShopResponse::from)
.toList()
);
}

Expand All @@ -23,13 +26,17 @@ private record InnerShopResponse(
Integer id,

@Schema(description = "상점 이름", example = "수신반점")
String name
String name,

@Schema(example = "4인 이상 픽업서비스", description = "혜택 미리보기 문구")
String detail
) {

public static InnerShopResponse from(Shop shop) {
public static InnerShopResponse from(BenefitCategoryMap benefitCategoryMap) {
return new InnerShopResponse(
shop.getId(),
shop.getName()
benefitCategoryMap.getShop().getId(),
benefitCategoryMap.getShop().getName(),
benefitCategoryMap.getDetail()
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package in.koreatech.koin.admin.benefit.dto;

import static com.fasterxml.jackson.databind.PropertyNamingStrategies.*;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.util.List;

import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.global.validation.NotBlankElement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

@JsonNaming(SnakeCaseStrategy.class)
public record AdminModifyBenefitShopsRequest(
@NotNull(message = "혜택문구 변경정보 리스트는 필수입니다.")
@NotBlankElement(message = "혜택문구 변경정보 리스트는 빈 요소가 존재할 수 없습니다.")
List<InnerBenefitShopsRequest> modifyDetails
) {

@JsonNaming(SnakeCaseStrategy.class)
public record InnerBenefitShopsRequest(
@Schema(description = "상점혜택 매핑id", example = "2", requiredMode = REQUIRED)
@NotNull(message = "상점혜택 매핑id는 필수입니다.")
Integer shopBenefitMapId,

@Schema(description = "혜택 미리보기 문구", example = "4인 이상 픽업서비스", requiredMode = REQUIRED)
@NotBlank(message = "혜택 미리보기 문구는 필수입니다.")
@Size(min = 2, max = 15, message = "혜택 미리보기 문구는 최소 2자 최대 15자입니다.")
String detail
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package in.koreatech.koin.admin.benefit.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class BenefitMapNotFoundException extends DataNotFoundException {

private static final String DEFAULT_MESSAGE = "해당 혜택 카테고리에 존재하지 않는 입니다.";

public BenefitMapNotFoundException(String message) {
super(message);
}

public BenefitMapNotFoundException(String message, String detail) {
super(message, detail);
}

public static BenefitMapNotFoundException withDetail(String detail) {
return new BenefitMapNotFoundException(DEFAULT_MESSAGE, detail);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.CrudRepository;

import in.koreatech.koin.domain.benefit.model.BenefitCategoryMap;
import org.springframework.data.repository.query.Param;

public interface AdminBenefitCategoryMapRepository extends Repository<BenefitCategoryMap, Integer> {
public interface AdminBenefitCategoryMapRepository extends CrudRepository<BenefitCategoryMap, Integer> {
krSeonghyeon marked this conversation as resolved.
Show resolved Hide resolved

void save(BenefitCategoryMap benefitCategoryMap);
List<BenefitCategoryMap> findAllByIdIn(List<Integer> ids);

@Query("""
SELECT bcm
Expand All @@ -22,6 +22,16 @@ public interface AdminBenefitCategoryMapRepository extends Repository<BenefitCat
""")
List<BenefitCategoryMap> findAllByBenefitCategoryIdOrderByShopName(@Param("benefitId") Integer benefitId);

@Query("""
SELECT bcm
FROM BenefitCategoryMap bcm
WHERE bcm.benefitCategory.id = :benefitId AND bcm.shop.id IN :shopIds
""")
List<BenefitCategoryMap> findAllByBenefitCategoryIdAndShopIds(
@Param("benefitId") Integer benefitId,
@Param("shopIds") List<Integer> shopIds
);

@Modifying
@Query("""
DELETE FROM BenefitCategoryMap bcm
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package in.koreatech.koin.admin.benefit.service;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -16,9 +17,11 @@
import in.koreatech.koin.admin.benefit.dto.AdminDeleteShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryRequest;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitCategoryResponse;
import in.koreatech.koin.admin.benefit.dto.AdminModifyBenefitShopsRequest;
import in.koreatech.koin.admin.benefit.dto.AdminSearchBenefitShopsResponse;
import in.koreatech.koin.admin.benefit.exception.BenefitDuplicationException;
import in.koreatech.koin.admin.benefit.exception.BenefitLimitException;
import in.koreatech.koin.admin.benefit.exception.BenefitMapNotFoundException;
import in.koreatech.koin.admin.benefit.repository.AdminBenefitCategoryMapRepository;
import in.koreatech.koin.admin.benefit.repository.AdminBenefitCategoryRepository;
import in.koreatech.koin.admin.shop.repository.shop.AdminShopRepository;
Expand Down Expand Up @@ -90,29 +93,61 @@ public void deleteBenefitCategory(Integer categoryId) {
public AdminBenefitShopsResponse getBenefitShops(Integer benefitId) {
List<BenefitCategoryMap> benefitCategoryMaps =
adminBenefitCategoryMapRepository.findAllByBenefitCategoryIdOrderByShopName(benefitId);
List<Shop> shops = benefitCategoryMaps.stream()
.map(BenefitCategoryMap::getShop)
.toList();
return AdminBenefitShopsResponse.from(shops);
return AdminBenefitShopsResponse.from(benefitCategoryMaps);
}

@Transactional
public AdminCreateBenefitShopsResponse createBenefitShops(
Integer benefitId,
AdminCreateBenefitShopsRequest request
) {
List<Shop> shops = adminShopRepository.findAllByIdIn(request.shopIds());
BenefitCategory benefitCategory = adminBenefitCategoryRepository.getById(benefitId);
for (Shop shop : shops) {
BenefitCategoryMap benefitCategoryMap = BenefitCategoryMap.builder()
Map<Integer, String> shopIdToDetail = request.shopDetails().stream()
.collect(Collectors.toMap(
AdminCreateBenefitShopsRequest.InnerBenefitShopsRequest::shopId,
AdminCreateBenefitShopsRequest.InnerBenefitShopsRequest::detail
));
List<Shop> shops = adminShopRepository.findAllByIdIn(shopIdToDetail.keySet().stream().toList());

List<BenefitCategoryMap> benefitCategoryMaps = shops.stream()
.map(shop -> BenefitCategoryMap.builder()
.shop(shop)
.benefitCategory(benefitCategory)
.build();
adminBenefitCategoryMapRepository.save(benefitCategoryMap);
}
return AdminCreateBenefitShopsResponse.from(shops);
.detail(shopIdToDetail.get(shop.getId()))
.build()
)
.toList();
adminBenefitCategoryMapRepository.saveAll(benefitCategoryMaps);
return AdminCreateBenefitShopsResponse.from(benefitCategoryMaps);
}

@Transactional
public void modifyBenefitShops(AdminModifyBenefitShopsRequest request) {
Map<Integer, String> shopBenefitIdToDetail = request.modifyDetails().stream()
.collect(Collectors.toMap(
AdminModifyBenefitShopsRequest.InnerBenefitShopsRequest::shopBenefitMapId,
AdminModifyBenefitShopsRequest.InnerBenefitShopsRequest::detail
));

List<BenefitCategoryMap> benefitCategoryMaps =
adminBenefitCategoryMapRepository.findAllByIdIn(shopBenefitIdToDetail.keySet().stream().toList());

validateBenefitMapIds(shopBenefitIdToDetail, benefitCategoryMaps);
benefitCategoryMaps.forEach(map -> map.modifyDetail(shopBenefitIdToDetail.get(map.getId())));
}

private static void validateBenefitMapIds(
Map<Integer, String> shopBenefitIdToDetail,
List<BenefitCategoryMap> benefitCategoryMaps
) {
List<Integer> notFoundMapIds = shopBenefitIdToDetail.keySet().stream()
.filter(mapId -> benefitCategoryMaps.stream().noneMatch(map -> map.getId().equals(mapId)))
.toList();

if (!notFoundMapIds.isEmpty()) {
throw new BenefitMapNotFoundException("해당 혜택 카테고리에 존재하지 않는 상점이 포함되어 있습니다. shopBenefitMapId: " + notFoundMapIds);
}
}

@Transactional
public void deleteBenefitShops(Integer benefitId, AdminDeleteShopsRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ public BenefitCategoryMap(Shop shop, BenefitCategory benefitCategory, String det
this.benefitCategory = benefitCategory;
this.detail = detail;
}

public void modifyDetail(String detail) {
this.detail = detail;
}
}
Loading
Loading