Skip to content

Commit fbb19f8

Browse files
committed
feat: 최신쿠폰 더보기 + 인기 쿠폰 더보기 조회 -> 일반/환급쿠폰 추가
1 parent 5649eb0 commit fbb19f8

9 files changed

Lines changed: 241 additions & 45 deletions

File tree

src/main/generated/queryDsl/com/appcenter/marketplace/domain/coupon/dto/res/QCouponRes.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ public QCouponRes(Expression<Long> couponId, Expression<String> couponName, Expr
2727
super(CouponRes.class, new Class<?>[]{long.class, String.class, String.class, java.time.LocalDateTime.class, boolean.class, boolean.class, com.appcenter.marketplace.domain.member_coupon.CouponType.class}, couponId, couponName, couponDescription, deadLine, isAvailable, isMemberIssued, couponType);
2828
}
2929

30-
public QCouponRes(NumberExpression<Long> couponId, StringExpression couponName, NumberExpression<Long> marketId, StringExpression marketName, StringExpression address, StringExpression thumbnail, BooleanExpression isAvailable, BooleanExpression isMemberIssued, TemporalExpression<java.time.LocalDateTime> couponCreatedAt) {
31-
super(CouponRes.class, new Class<?>[]{long.class, String.class, long.class, String.class, String.class, String.class, boolean.class, boolean.class, java.time.LocalDateTime.class}, couponId, couponName, marketId, marketName, address, thumbnail, isAvailable, isMemberIssued, couponCreatedAt);
30+
public QCouponRes(Expression<Long> couponId, Expression<String> couponName, Expression<Long> marketId, Expression<String> marketName, Expression<String> address, Expression<String> thumbnail, Expression<Boolean> isAvailable, Expression<Boolean> isMemberIssued, Expression<java.time.LocalDateTime> couponCreatedAt, Expression<com.appcenter.marketplace.domain.member_coupon.CouponType> couponType) {
31+
super(CouponRes.class, new Class<?>[]{long.class, String.class, long.class, String.class, String.class, String.class, boolean.class, boolean.class, java.time.LocalDateTime.class, com.appcenter.marketplace.domain.member_coupon.CouponType.class}, couponId, couponName, marketId, marketName, address, thumbnail, isAvailable, isMemberIssued, couponCreatedAt, couponType);
3232
}
3333

34-
public QCouponRes(NumberExpression<Long> couponId, StringExpression couponName, NumberExpression<Long> marketId, StringExpression marketName, StringExpression address, StringExpression thumbnail, BooleanExpression isAvailable, BooleanExpression isMemberIssued, NumberExpression<Long> issuedCount) {
35-
super(CouponRes.class, new Class<?>[]{long.class, String.class, long.class, String.class, String.class, String.class, boolean.class, boolean.class, long.class}, couponId, couponName, marketId, marketName, address, thumbnail, isAvailable, isMemberIssued, issuedCount);
34+
public QCouponRes(NumberExpression<Long> couponId, StringExpression couponName, NumberExpression<Long> marketId, StringExpression marketName, StringExpression address, StringExpression thumbnail, BooleanExpression isAvailable, BooleanExpression isMemberIssued, NumberExpression<Long> issuedCount, EnumExpression<com.appcenter.marketplace.domain.member_coupon.CouponType> couponType) {
35+
super(CouponRes.class, new Class<?>[]{long.class, String.class, long.class, String.class, String.class, String.class, boolean.class, boolean.class, long.class, com.appcenter.marketplace.domain.member_coupon.CouponType.class}, couponId, couponName, marketId, marketName, address, thumbnail, isAvailable, isMemberIssued, issuedCount, couponType);
36+
}
37+
38+
public QCouponRes(Expression<Long> couponId, Expression<String> couponName, Expression<Long> marketId, Expression<String> marketName, Expression<String> address, Expression<String> thumbnail, Expression<Boolean> isAvailable, Expression<Boolean> isMemberIssued, Expression<Long> issuedCount, Expression<Integer> orderNo, Expression<com.appcenter.marketplace.domain.member_coupon.CouponType> couponType) {
39+
super(CouponRes.class, new Class<?>[]{long.class, String.class, long.class, String.class, String.class, String.class, boolean.class, boolean.class, long.class, int.class, com.appcenter.marketplace.domain.member_coupon.CouponType.class}, couponId, couponName, marketId, marketName, address, thumbnail, isAvailable, isMemberIssued, issuedCount, orderNo, couponType);
3640
}
3741

3842
}

src/main/java/com/appcenter/marketplace/domain/coupon/controller/CouponController.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.appcenter.marketplace.domain.coupon.dto.res.*;
44
import com.appcenter.marketplace.domain.coupon.service.CouponService;
5+
import com.appcenter.marketplace.domain.member_coupon.CouponType;
56
import com.appcenter.marketplace.global.common.CommonResponse;
67
import io.swagger.v3.oas.annotations.Operation;
78
import io.swagger.v3.oas.annotations.Parameter;
@@ -45,8 +46,9 @@ public ResponseEntity<CommonResponse<CouponPageRes<CouponRes>>> getCouponList(
4546
}
4647

4748
@Operation(summary = "최신 등록 쿠폰 더보기 조회",
48-
description = "최신 쿠폰을 등록순으로 정렬 <br>" +
49-
"처음 요청 시, pageSize만 보내면 됩니다. (기본값은 10입니다) <br>"
49+
description = "최신 쿠폰을 등록순으로 정렬 (일반 쿠폰 + 환급 쿠폰 통합) <br>" +
50+
"처음 요청 시, pageSize만 보내면 됩니다. (기본값은 10입니다) <br>" +
51+
"다음 페이지 요청 시: lastCreatedAt, lastCouponId, couponType 필수 <br>"
5052
)
5153
@GetMapping("/latest")
5254
public ResponseEntity<CommonResponse<CouponPageRes<CouponRes>>> getLatestCoupon(
@@ -55,32 +57,39 @@ public ResponseEntity<CommonResponse<CouponPageRes<CouponRes>>> getLatestCoupon(
5557
@RequestParam(required = false, name = "lastCreatedAt") LocalDateTime lastCreatedAt,
5658
@Parameter(description = "각 페이지의 마지막 couponId (e.g. 5)")
5759
@RequestParam(required = false, name = "lastCouponId") Long couponId,
60+
@Parameter(description = "각 페이지의 마지막 쿠폰 타입 (GIFT 또는 PAYBACK)")
61+
@RequestParam(required = false, name = "couponType") CouponType couponType,
5862
@RequestParam(defaultValue = "10", name = "pageSize") Integer size) {
5963

6064
Long memberId = extractMemberId(authentication);
6165
return ResponseEntity
6266
.ok(CommonResponse.from(MARKET_FOUND.getMessage(),
63-
couponService.getLatestCouponPage(memberId, lastCreatedAt, couponId,size)));
67+
couponService.getLatestCouponPage(memberId, lastCreatedAt, couponId, couponType, size)));
6468
}
6569

6670
@Operation(summary = "인기 쿠폰 더보기 조회",
67-
description = "인기 쿠폰을 등록순으로 정렬 <br>" +
68-
"처음 요청 시, pageSize만 보내면 됩니다. (기본값은 10입니다) <br>"
71+
description = "인기 쿠폰을 발급 횟수 순 + 환급 쿠폰을 매장 순서로 정렬 <br>" +
72+
"처음 요청 시, pageSize만 보내면 됩니다. (기본값은 10입니다) <br>" +
73+
"다음 페이지 요청 시: <br>" +
74+
"- 일반 쿠폰(GIFT): lastIssuedCount, lastCouponId, couponType=GIFT <br>" +
75+
"- 환급 쿠폰(PAYBACK): lastOrderNo (응답의 orderNo 값), lastCouponId, couponType=PAYBACK <br>"
6976
)
7077
@GetMapping("/popular")
7178
public ResponseEntity<CommonResponse<CouponPageRes<CouponRes>>> getPopularCoupon(
7279
Authentication authentication,
73-
@Parameter(description = "페이지의 마지막 issuedCount")
80+
@Parameter(description = "페이지의 마지막 issuedCount (일반 쿠폰) 또는 orderNo (환급 쿠폰)")
7481
@RequestParam(required = false, name = "lastIssuedCount") Long count,
7582
@Parameter(description = "각 페이지의 마지막 couponId (e.g. 5)")
7683
@RequestParam(required = false, name = "lastCouponId") Long couponId,
84+
@Parameter(description = "각 페이지의 마지막 쿠폰 타입 (GIFT 또는 PAYBACK)")
85+
@RequestParam(required = false, name = "couponType") CouponType couponType,
7786
@RequestParam(defaultValue = "10", name = "pageSize") Integer size) {
7887

7988
Long memberId = extractMemberId(authentication);
8089

8190
return ResponseEntity
8291
.ok(CommonResponse.from(MARKET_FOUND.getMessage(),
83-
couponService.getPopularCouponPage(memberId, count, couponId,size)));
92+
couponService.getPopularCouponPage(memberId, count, couponId, couponType, size)));
8493
}
8594

8695
@Operation(summary = "마감 임박 쿠폰 TOP 조회",

src/main/java/com/appcenter/marketplace/domain/coupon/dto/res/CouponRes.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class CouponRes {
3131
private String thumbnail;
3232
private LocalDateTime couponCreatedAt;
3333
private Long issuedCount;
34+
private Integer orderNo; // 매장 순서 (Payback 페이징용)
3435

3536
// 사장님 매장 쿠폰 페이징 조회
3637
@QueryProjection
@@ -72,7 +73,7 @@ public CouponRes(Long couponId, String couponName, String couponDescription, Loc
7273

7374
// 최신 쿠폰 페이징 조회
7475
@QueryProjection
75-
public CouponRes(Long couponId, String couponName, Long marketId, String marketName, String address, String thumbnail, Boolean isAvailable, Boolean isMemberIssued, LocalDateTime couponCreatedAt) {
76+
public CouponRes(Long couponId, String couponName, Long marketId, String marketName, String address, String thumbnail, Boolean isAvailable, Boolean isMemberIssued, LocalDateTime couponCreatedAt, CouponType couponType) {
7677
this.couponId = couponId;
7778
this.couponName = couponName;
7879
this.marketId = marketId;
@@ -82,11 +83,12 @@ public CouponRes(Long couponId, String couponName, Long marketId, String marketN
8283
this.isAvailable = isAvailable;
8384
this.isMemberIssued = isMemberIssued;
8485
this.couponCreatedAt = couponCreatedAt;
86+
this.couponType = couponType;
8587
}
8688

8789
// 인기 쿠폰 페이징 조회
8890
@QueryProjection
89-
public CouponRes(Long couponId, String couponName, Long marketId, String marketName, String address, String thumbnail, Boolean isAvailable, Boolean isMemberIssued, Long issuedCount) {
91+
public CouponRes(Long couponId, String couponName, Long marketId, String marketName, String address, String thumbnail, Boolean isAvailable, Boolean isMemberIssued, Long issuedCount, CouponType couponType) {
9092
this.couponId = couponId;
9193
this.couponName = couponName;
9294
this.marketId = marketId;
@@ -96,6 +98,23 @@ public CouponRes(Long couponId, String couponName, Long marketId, String marketN
9698
this.isAvailable = isAvailable;
9799
this.isMemberIssued = isMemberIssued;
98100
this.issuedCount = issuedCount;
101+
this.couponType = couponType;
102+
}
103+
104+
// 인기 쿠폰 페이징 조회 (orderNo 포함)
105+
@QueryProjection
106+
public CouponRes(Long couponId, String couponName, Long marketId, String marketName, String address, String thumbnail, Boolean isAvailable, Boolean isMemberIssued, Long issuedCount, Integer orderNo, CouponType couponType) {
107+
this.couponId = couponId;
108+
this.couponName = couponName;
109+
this.marketId = marketId;
110+
this.marketName = marketName;
111+
this.address = address;
112+
this.thumbnail = thumbnail;
113+
this.isAvailable = isAvailable;
114+
this.isMemberIssued = isMemberIssued;
115+
this.issuedCount = issuedCount;
116+
this.orderNo = orderNo;
117+
this.couponType = couponType;
99118
}
100119

101120
public static CouponRes toDto(Coupon coupon){

src/main/java/com/appcenter/marketplace/domain/coupon/repository/CouponRepositoryCustomImpl.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public List<CouponRes> findLatestCouponList(Long memberId, LocalDateTime lastCre
8484
memberId != null ?
8585
memberCoupon.id.isNotNull() :
8686
Expressions.FALSE,
87-
coupon.createdAt
88-
))
87+
coupon.createdAt,
88+
Expressions.asEnum(CouponType.GIFT)))
8989
.from(coupon)
9090
.innerJoin(coupon.market, market)
9191
.innerJoin(local).on(market.local.eq(local))
@@ -118,7 +118,9 @@ public List<CouponRes> findPopularCouponList(Long memberId, Long count, Long cou
118118
memberId != null ?
119119
issuedCoupon.id.isNotNull() :
120120
Expressions.FALSE,
121-
memberCoupon.id.count()))
121+
memberCoupon.id.count(),
122+
Expressions.constant(0), // orderNo = 0 (GIFT는 orderNo 사용 안함)
123+
Expressions.asEnum(CouponType.GIFT)))
122124
.from(coupon)
123125
.innerJoin(coupon.market, market)
124126
.innerJoin(local).on(market.local.eq(local))
@@ -155,7 +157,7 @@ public List<TopClosingCouponRes> findTopClosingCouponList(Integer size) {
155157
market.id,
156158
market.name,
157159
market.thumbnail,
158-
Expressions.constant(CouponType.GIFT)))
160+
Expressions.asEnum(CouponType.GIFT)))
159161
.from(coupon)
160162
.innerJoin(coupon.market, market)
161163
.where(coupon.isDeleted.eq(false)
@@ -178,7 +180,7 @@ public List<TopLatestCouponRes> findTopLatestCouponList(Integer size) {
178180
market.name,
179181
market.thumbnail,
180182
coupon.createdAt,
181-
Expressions.constant(CouponType.GIFT)
183+
Expressions.asEnum(CouponType.GIFT)
182184
))
183185
.from(coupon)
184186
.innerJoin(coupon.market, market)
@@ -201,7 +203,7 @@ public List<TopPopularCouponRes> findTopPopularCouponList(Integer size) {
201203
market.name,
202204
market.thumbnail,
203205
memberCoupon.id.count(),
204-
Expressions.constant(CouponType.GIFT)))
206+
Expressions.asEnum(CouponType.GIFT)))
205207
.from(coupon)
206208
.innerJoin(coupon.market, market)
207209
.leftJoin(memberCoupon).on(coupon.eq(memberCoupon.coupon))
@@ -273,7 +275,7 @@ public List<CouponRes> findCouponsForAdmin(Long couponId, Long marketId, Integer
273275
coupon.isHidden,
274276
market.id,
275277
market.name,
276-
Expressions.constant(CouponType.GIFT)))
278+
Expressions.asEnum(CouponType.GIFT)))
277279
.from(coupon)
278280
.innerJoin(coupon.market, market)
279281
.innerJoin(market.local, local)

src/main/java/com/appcenter/marketplace/domain/coupon/service/CouponService.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.appcenter.marketplace.domain.coupon.service;
22

33
import com.appcenter.marketplace.domain.coupon.dto.res.*;
4+
import com.appcenter.marketplace.domain.member_coupon.CouponType;
45

56
import java.time.LocalDateTime;
67
import java.util.List;
@@ -9,9 +10,9 @@ public interface CouponService {
910

1011
CouponPageRes<CouponRes> getCouponList(Long memberId, Long marketId, Long couponId, Integer size);
1112

12-
CouponPageRes<CouponRes> getLatestCouponPage(Long memberId, LocalDateTime createdAt, Long couponId, Integer size);
13+
CouponPageRes<CouponRes> getLatestCouponPage(Long memberId, LocalDateTime createdAt, Long couponId, CouponType couponType, Integer size);
1314

14-
CouponPageRes<CouponRes> getPopularCouponPage(Long memberId, Long count, Long couponId, Integer size);
15+
CouponPageRes<CouponRes> getPopularCouponPage(Long memberId, Long count, Long couponId, CouponType couponType, Integer size);
1516

1617
List<TopClosingCouponRes> getTopClosingCoupon(Integer size);
1718

src/main/java/com/appcenter/marketplace/domain/coupon/service/impl/CouponServiceImpl.java

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.appcenter.marketplace.domain.coupon.service.CouponService;
66
import com.appcenter.marketplace.domain.market.Market;
77
import com.appcenter.marketplace.domain.market.repository.MarketRepository;
8+
import com.appcenter.marketplace.domain.member_coupon.CouponType;
89
import com.appcenter.marketplace.domain.payback.repository.PaybackRepository;
910
import com.appcenter.marketplace.global.exception.CustomException;
1011
import lombok.RequiredArgsConstructor;
@@ -14,7 +15,10 @@
1415

1516
import java.time.LocalDateTime;
1617
import java.util.ArrayList;
18+
import java.util.Comparator;
1719
import java.util.List;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.Stream;
1822

1923
import static com.appcenter.marketplace.global.common.StatusCode.MARKET_NOT_EXIST;
2024

@@ -34,19 +38,68 @@ public CouponPageRes<CouponRes> getCouponList(Long memberId, Long marketId, Long
3438
return checkNextPageAndReturn(couponList, size);
3539
}
3640

37-
// 최신 등록 쿠폰 더보기 조회
41+
// 최신 등록 쿠폰 더보기 조회 (Coupon + Payback 병합, createdAt 순)
3842
@Override
39-
public CouponPageRes<CouponRes> getLatestCouponPage(Long memberId, LocalDateTime lastCreatedAt, Long couponId, Integer size) {
40-
List<CouponRes> resDtoList = couponRepository.findLatestCouponList(memberId, lastCreatedAt, couponId, size);
41-
42-
return checkNextPageAndReturn(resDtoList, size);
43+
public CouponPageRes<CouponRes> getLatestCouponPage(Long memberId, LocalDateTime lastCreatedAt, Long couponId, CouponType couponType, Integer size) {
44+
// Coupon과 Payback을 각각 조회 (size + 1개씩)
45+
List<CouponRes> coupons = couponRepository.findLatestCouponList(memberId, lastCreatedAt, couponId, size + 1);
46+
List<CouponRes> paybacks = paybackRepository.findLatestPaybackList(memberId, lastCreatedAt, couponId, size + 1);
47+
48+
// createdAt 기준으로 병합 정렬
49+
List<CouponRes> mergedList = Stream.concat(coupons.stream(), paybacks.stream())
50+
.sorted(Comparator.comparing(CouponRes::getCouponCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()))
51+
.thenComparing(CouponRes::getCouponId, Comparator.reverseOrder()))
52+
.collect(Collectors.toList());
53+
54+
// 커서 필터링 (lastCreatedAt, couponId, couponType 기준)
55+
List<CouponRes> filteredList = mergedList.stream()
56+
.filter(c -> {
57+
if (lastCreatedAt == null || couponId == null || couponType == null) {
58+
return true;
59+
}
60+
// 이전 커서 이후의 데이터만 포함
61+
if (c.getCouponCreatedAt().isBefore(lastCreatedAt)) {
62+
return true;
63+
} else if (c.getCouponCreatedAt().equals(lastCreatedAt)) {
64+
// 같은 createdAt인 경우, couponType과 id로 판단
65+
if (c.getCouponType() == couponType) {
66+
return c.getCouponId() < couponId;
67+
}
68+
return c.getCouponId() <= couponId;
69+
}
70+
return false;
71+
})
72+
.limit(size + 1)
73+
.collect(Collectors.toList());
74+
75+
return checkNextPageAndReturn(filteredList, size);
4376
}
4477

45-
// 인기 쿠폰 더보기 조회
78+
// 인기 쿠폰 더보기 조회 (Coupon 발급 횟수 순 → Payback 매장 orderNo 순)
4679
@Override
47-
public CouponPageRes<CouponRes> getPopularCouponPage(Long memberId, Long count, Long couponId, Integer size) {
48-
List<CouponRes> resDtoList = couponRepository.findPopularCouponList(memberId, count, couponId, size);
49-
return checkNextPageAndReturn(resDtoList, size);
80+
public CouponPageRes<CouponRes> getPopularCouponPage(Long memberId, Long count, Long couponId, CouponType couponType, Integer size) {
81+
List<CouponRes> result = new ArrayList<>();
82+
83+
if (couponType == null || couponType == CouponType.GIFT) {
84+
// Coupon 조회
85+
List<CouponRes> coupons = couponRepository.findPopularCouponList(memberId, count, couponId, size + 1);
86+
result.addAll(coupons);
87+
88+
// Coupon이 size보다 적으면 Payback 추가
89+
if (result.size() <= size) {
90+
int remainingSize = size + 1 - result.size();
91+
List<CouponRes> paybacks = paybackRepository.findPopularPaybackList(memberId, null, null, remainingSize);
92+
result.addAll(paybacks);
93+
}
94+
} else {
95+
// PAYBACK 타입이면 Payback만 조회
96+
// count 값을 orderNo로 해석
97+
Integer orderNo = count != null ? count.intValue() : null;
98+
List<CouponRes> paybacks = paybackRepository.findPopularPaybackList(memberId, orderNo, couponId, size + 1);
99+
result.addAll(paybacks);
100+
}
101+
102+
return checkNextPageAndReturn(result, size);
50103
}
51104

52105
// 마감 임박 쿠폰 TOP 조회 (일반 쿠폰 + Payback 쿠폰)

0 commit comments

Comments
 (0)