diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationCommandService.java b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationCommandService.java index ebdf12fd..34f81575 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationCommandService.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationCommandService.java @@ -196,7 +196,8 @@ private String getMainCouponName(Long accommodationId) { public AccommodationDetailResponse findAccommodationWithRooms( Long accommodationId, LocalDate startDate, LocalDate endDate ) { - Accommodation accommodation = findAccommodationById(accommodationId); + Accommodation accommodation = accommodationQueryUseCase.getAccommodationById( + accommodationId); List filterRooms = getFilteredRoomsByDate( accommodation.getRooms(), startDate, endDate @@ -216,13 +217,12 @@ room, getDiscountPrice(room), ); } - private Accommodation findAccommodationById(Long accommodationId) { - Accommodation accommodation = - accommodationRepository.findById(accommodationId) - .orElseThrow(AccommodationNotFoundException::new); - return accommodation; + @Transactional(readOnly = true) + public Accommodation findAccommodationByRoomId(long roomId) { + return roomQueryService.findRoomById(roomId).getAccommodation(); } + private int getDiscountPrice(Room room) { return couponService.getSortedTotalCouponResponseInRoom(room) .stream() diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationQueryService.java b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationQueryService.java index 1959378c..24b211c3 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationQueryService.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/AccommodationQueryService.java @@ -49,6 +49,12 @@ public Accommodation getAccommodationById(long accommodationId) { .orElseThrow(AccommodationNotFoundException::new); } + @Override + @Transactional(readOnly = true) + public boolean existsById(Long accommodationId) { + return accommodationRepository.existsById(accommodationId); + } + @Override public AccommodationOwnership saveOwnership(Member member, Accommodation accommodation) { return accommodationOwnershipRepository.save(AccommodationOwnership.builder() @@ -74,7 +80,7 @@ public boolean existsOwnershipByMemberAndAccommodation(Member member, @Override @Transactional(readOnly = true) public Category getCategoryByName(String name) { - return categoryRepository.findCategoryByName(name) + return categoryRepository.findCategoryByNameAndIdGreaterThan(name, 4L) .orElseThrow(WrongCategoryException::new); } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/usecase/AccommodationQueryUseCase.java b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/usecase/AccommodationQueryUseCase.java index a7657523..2729a5b8 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/usecase/AccommodationQueryUseCase.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/accommodation/service/usecase/AccommodationQueryUseCase.java @@ -16,6 +16,8 @@ public interface AccommodationQueryUseCase { Accommodation getAccommodationById(long id); + boolean existsById(Long accommodationId); + AccommodationOwnership saveOwnership(Member member, Accommodation accommodation); List getOwnershipByMember(Member member); diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/entity/CouponIssuance.java b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/entity/CouponIssuance.java index 1bd083a6..b02ade8f 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/entity/CouponIssuance.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/entity/CouponIssuance.java @@ -1,5 +1,6 @@ package com.backoffice.upjuyanolja.domain.coupon.entity; +import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; import com.backoffice.upjuyanolja.domain.room.entity.Room; import com.backoffice.upjuyanolja.global.common.entity.BaseTime; import jakarta.persistence.Column; @@ -40,6 +41,11 @@ public class CouponIssuance extends BaseTime { @Comment("객실 식별자") private Room room; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(nullable = false, name = "point_usage_id") + @Comment("객실 식별자") + private PointUsage pointUsage; + @Column(nullable = false) @Comment("발급 수량") private int quantity; @@ -53,13 +59,16 @@ public CouponIssuance( Long id, Coupon coupon, Room room, + PointUsage pointUsage, int quantity, int amount ) { this.id = id; this.coupon = coupon; this.room = room; + this.pointUsage = pointUsage; this.quantity = quantity; this.amount = amount; } + } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/repository/CouponIssuanceRepository.java b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/repository/CouponIssuanceRepository.java index 311cb315..d92f05af 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/repository/CouponIssuanceRepository.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/repository/CouponIssuanceRepository.java @@ -1,8 +1,12 @@ package com.backoffice.upjuyanolja.domain.coupon.repository; import com.backoffice.upjuyanolja.domain.coupon.entity.CouponIssuance; +import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface CouponIssuanceRepository extends JpaRepository { + List findByPointUsage(PointUsage pointUsage); + } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponBackofficeService.java b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponBackofficeService.java index 2544fedf..26ab7066 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponBackofficeService.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponBackofficeService.java @@ -6,7 +6,7 @@ import static java.util.stream.Collectors.toList; import com.backoffice.upjuyanolja.domain.accommodation.exception.AccommodationNotFoundException; -import com.backoffice.upjuyanolja.domain.accommodation.repository.AccommodationRepository; +import com.backoffice.upjuyanolja.domain.accommodation.service.usecase.AccommodationQueryUseCase; import com.backoffice.upjuyanolja.domain.coupon.dto.request.backoffice.CouponAddInfos; import com.backoffice.upjuyanolja.domain.coupon.dto.request.backoffice.CouponAddRequest; import com.backoffice.upjuyanolja.domain.coupon.dto.request.backoffice.CouponDeleteRequest; @@ -22,13 +22,11 @@ import com.backoffice.upjuyanolja.domain.coupon.entity.Coupon; import com.backoffice.upjuyanolja.domain.coupon.entity.CouponIssuance; import com.backoffice.upjuyanolja.domain.coupon.entity.DiscountType; -import com.backoffice.upjuyanolja.domain.coupon.exception.InsufficientPointsException; import com.backoffice.upjuyanolja.domain.coupon.exception.InvalidCouponInfoException; import com.backoffice.upjuyanolja.domain.coupon.repository.CouponIssuanceRepository; import com.backoffice.upjuyanolja.domain.coupon.repository.CouponRepository; -import com.backoffice.upjuyanolja.domain.point.entity.Point; -import com.backoffice.upjuyanolja.domain.point.exception.PointNotFoundException; -import com.backoffice.upjuyanolja.domain.point.repository.PointRepository; +import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; +import com.backoffice.upjuyanolja.domain.point.service.PointService; import com.backoffice.upjuyanolja.domain.room.entity.Room; import com.backoffice.upjuyanolja.domain.room.repository.RoomRepository; import com.backoffice.upjuyanolja.global.exception.NotOwnerException; @@ -49,10 +47,11 @@ public class CouponBackofficeService { private final CouponRepository couponRepository; private final RoomRepository roomRepository; - private final PointRepository pointRepository; - private final AccommodationRepository accommodationRepository; private final CouponIssuanceRepository couponIssuanceRepository; + private final AccommodationQueryUseCase accommodationQueryUseCase; + private final PointService pointService; + // 쿠폰 만들기 View Response public CouponMakeViewResponse getRoomsByAccommodation(Long accommodationId) { return couponRepository.findRoomsByAccommodationId(accommodationId); @@ -63,7 +62,10 @@ public void createCoupon( final CouponMakeRequest couponMakeRequest, final Long memberId ) { final long totalPoints = couponMakeRequest.totalPoints(); - Point point = validationPoint(memberId, totalPoints); + pointService.validatePoint(memberId, totalPoints); + + // 업주의 보유 포인트 차감 + PointUsage pointUsage = pointService.usePointForCoupon(memberId, totalPoints); List couponRooms = couponMakeRequest.rooms(); List coupons = new ArrayList<>(); @@ -98,19 +100,14 @@ public void createCoupon( log.info("신규 쿠폰 발급: {}, memberId: {}", quantity, memberId); } // 5. 쿠폰 발급 내역 배열 생성 - couponIssuances.add(createCouponIssuance(room, coupon, quantity, amount)); + couponIssuances.add(createCouponIssuance(room, coupon, pointUsage, quantity, amount)); } // 6. 생성된 쿠폰 저장 couponRepository.saveAll(coupons); // 7. 생성된 쿠폰 발급 내역 저장 couponIssuanceRepository.saveAll(couponIssuances); - // 8. 업주의 보유 포인트 차감 - // todo: 도메인이 다른 서비스를 트랜잭션 안에서 호출하는 게 좋은 설계일까 고민해 보기. - point.decreasePointBalance(totalPoints); - pointRepository.save(point); - - // 9. 포인트 사용 이력 전달 + // 8. 포인트 사용 이력 전달 // Todo: 포인트 사용 내역 Point 도메인에 전달하기 log.info("쿠폰 발급 성공. 금액: {}", totalPoints); } @@ -146,27 +143,39 @@ public CouponManageResponse manageCoupon(final Long accommodationId) { public void addonCoupon(final CouponAddRequest couponAddRequest, final long memberId) { // 1. 업주의 보유 포인트 검증 final long totalPoints = couponAddRequest.totalPoints(); - Point point = validationPoint(memberId, totalPoints); + pointService.validatePoint(memberId, totalPoints); + + // 2. 업주의 보유 포인트 차감 + PointUsage pointUsage = pointService.usePointForCoupon(memberId, totalPoints); List addCoupons = new ArrayList<>(); + List addCouponIssuances = new ArrayList<>(); + for (var rooms : couponAddRequest.rooms()) { for (var coupons : rooms.coupons()) { addCoupons.add(increaseCouponStock(coupons)); + + Room room = roomRepository.findById(rooms.roomId()).orElseThrow( + InvalidCouponInfoException::new); + Coupon coupon = couponRepository.findById(coupons.couponId()).orElseThrow( + InvalidCouponInfoException::new); + int quantity = coupons.buyQuantity(); + int amount = coupons.eachPoint(); + + addCouponIssuances.add( + (createCouponIssuance(room, coupon, pointUsage, quantity, amount))); } } couponRepository.saveAll(addCoupons); + couponIssuanceRepository.saveAll(addCouponIssuances); - // 2. 업주의 보유 포인트 차감 - point.decreasePointBalance(totalPoints); - pointRepository.save(point); - // Todo: 포인트 사용 내역 Point 도메인에 전달하기 log.info("쿠폰 추가 발급 성공. 금액: {}", totalPoints); } // 쿠폰 수정 public void modifyCoupon(final CouponModifyRequest modifyRequest) { - + List modifyCoupons = new ArrayList<>(); for (var rooms : modifyRequest.rooms()) { for (var coupons : rooms.coupons()) { @@ -245,21 +254,6 @@ private CouponManageRooms createManageRoom(final CouponManageQueryDto dto) { .build(); } - // 업주의 보유 포인트 검증 - @Transactional(readOnly = true) - protected Point validationPoint(final Long memberId, final long totalPoints) { - final Optional resultPoint = pointRepository.findByMemberId(memberId); - Point point = resultPoint.orElseThrow(PointNotFoundException::new); - final long ownerPoint = point.getTotalPointBalance(); - - // 쿠폰 구매 요청 금액이 업주의 보유 포인트보다 크다면 예외 발생 - if (ownerPoint < totalPoints) { - log.info("업주의 보유 포인트가 부족합니다. 보유 포인트: {}, 요청 포인트: {}", - ownerPoint, totalPoints); - throw new InsufficientPointsException(); - } - return point; - } // 정상적인 숙소 id 요청인지 검증 @Transactional(readOnly = true) @@ -272,7 +266,8 @@ public void validateAccommodationRequest( currentMemberId, accommodationId); throw new NotOwnerException(); } - if (!accommodationRepository.existsById(accommodationId)) { + + if (!accommodationQueryUseCase.existsById(accommodationId)) { log.info("숙소의 정보를 찾을 수 없습니다. id: {}", accommodationId); throw new AccommodationNotFoundException(); } @@ -286,12 +281,14 @@ private Coupon updateCouponStock(final Coupon coupon, final int quantity) { private CouponIssuance createCouponIssuance( final Room room, final Coupon coupon, + final PointUsage pointUsage, final int quantity, final int amount ) { return CouponIssuance.builder() .room(room) .coupon(coupon) + .pointUsage(pointUsage) .quantity(quantity) .amount(amount) .build(); diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponIssuanceGetService.java b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponIssuanceGetService.java new file mode 100644 index 00000000..28d86efb --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/coupon/service/CouponIssuanceGetService.java @@ -0,0 +1,20 @@ +package com.backoffice.upjuyanolja.domain.coupon.service; + +import com.backoffice.upjuyanolja.domain.coupon.entity.CouponIssuance; +import com.backoffice.upjuyanolja.domain.coupon.repository.CouponIssuanceRepository; +import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CouponIssuanceGetService { + + private final CouponIssuanceRepository couponIssuanceRepository; + + public List getCouponIssuanceByPointUsage(PointUsage pointUsage) { + return couponIssuanceRepository.findByPointUsage(pointUsage); + } + +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/controller/PointController.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/controller/PointController.java index 172b0d71..2db11897 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/controller/PointController.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/controller/PointController.java @@ -5,6 +5,8 @@ import com.backoffice.upjuyanolja.domain.point.dto.response.PointChargeResponse; import com.backoffice.upjuyanolja.domain.point.dto.response.PointSummaryResponse; import com.backoffice.upjuyanolja.domain.point.dto.response.PointTotalBalanceResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointTotalPageResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsagePageResponse; import com.backoffice.upjuyanolja.domain.point.service.PointService; import com.backoffice.upjuyanolja.global.security.SecurityUtil; import jakarta.validation.Valid; @@ -51,9 +53,9 @@ public ResponseEntity getPointSummary( return ResponseEntity.status(HttpStatus.OK).body(response); } - @GetMapping("/total") + @GetMapping("/total-balance") public ResponseEntity getPointTotalBalance() { - log.info("GET /api/points/total"); + log.info("GET /api/points/total-balance"); PointTotalBalanceResponse response = pointService.getPointTotalBalanceResponse( securityUtil.getCurrentMemberId() @@ -68,7 +70,7 @@ public ResponseEntity getChargePoints( ) { log.info("Get /api/points/charges"); - PointChargePageResponse response = pointService.getChargePoints( + PointChargePageResponse response = pointService.getPointChargePageResponse( securityUtil.getCurrentMemberId(), pageable ); @@ -81,7 +83,33 @@ public ResponseEntity getDetailChargePoints( ) { log.info("Get /api/points/charges/{chargeId}"); - PointChargeResponse response = pointService.getDetailChargePoint(chargeId); + PointChargeResponse response = pointService.getDetailChargePointResponse(chargeId); + + return ResponseEntity.status(HttpStatus.OK).body(response); + } + + @GetMapping("/usages") + public ResponseEntity getUsagePoints( + @PageableDefault(page = 0, size = 4) Pageable pageable + ) { + log.info("Get /api/points/usages"); + + PointUsagePageResponse response = pointService.getPointUsagePageResponse( + securityUtil.getCurrentMemberId(), pageable + ); + + return ResponseEntity.status(HttpStatus.OK).body(response); + } + + @GetMapping("/total") + public ResponseEntity getTotalPoints( + @PageableDefault(page = 0, size = 4) Pageable pageable + ) { + log.info("Get /api/points/total"); + + PointTotalPageResponse response = pointService.getTotalPointPageResponse( + securityUtil.getCurrentMemberId(), pageable + ); return ResponseEntity.status(HttpStatus.OK).body(response); } @@ -92,7 +120,7 @@ public ResponseEntity chargePoint( ) { log.info("Post /api/points/charges"); - PointChargeResponse response = pointService.getChargePointResponse( + PointChargeResponse response = pointService.chargePoint( securityUtil.getCurrentMemberId(), request ); diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointChargeResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointChargeResponse.java index 4398b767..8ed66f64 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointChargeResponse.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointChargeResponse.java @@ -1,6 +1,7 @@ package com.backoffice.upjuyanolja.domain.point.dto.response; import com.backoffice.upjuyanolja.domain.point.entity.PointCharges; +import java.time.format.DateTimeFormatter; import lombok.Builder; @Builder @@ -16,7 +17,7 @@ public static PointChargeResponse of( ) { return PointChargeResponse.builder() .orderId(pointCharges.getOrderName()) - .tradeAt(pointCharges.getChargeDate().toString()) + .tradeAt(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(pointCharges.getChargeDate())) .orderName(pointCharges.getChargePoint()+"원") .amount(pointCharges.getChargePoint()) .build(); diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalDetailResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalDetailResponse.java new file mode 100644 index 00000000..b7f35966 --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalDetailResponse.java @@ -0,0 +1,55 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import java.time.LocalDateTime; +import lombok.Builder; + +@Builder +public record PointTotalDetailResponse( + long id, + String category, + String type, + String status, + String name, + String description, + long trade, + long amount, + String date, + Object receipt + +) { + + public static PointTotalDetailResponse of( + String category, String type, String status, + String name, String description, long trade, + long amount, String date, Object receipt + ) { + return PointTotalDetailResponse.builder() + .category(category) + .type(type) + .status(status) + .name(name) + .description(description) + .trade(trade) + .amount(amount) + .date(date) + .receipt(receipt) + .build(); + } + + public static PointTotalDetailResponse from( + long id, PointTotalDetailResponse pointTotalDetailResponse + ) { + return PointTotalDetailResponse.builder() + .id(id) + .category(pointTotalDetailResponse.category) + .type(pointTotalDetailResponse.type) + .status(pointTotalDetailResponse.status) + .name(pointTotalDetailResponse.name) + .description(pointTotalDetailResponse.description) + .trade(pointTotalDetailResponse.trade) + .amount(pointTotalDetailResponse.amount) + .date(pointTotalDetailResponse.date) + .receipt(pointTotalDetailResponse.receipt) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalPageResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalPageResponse.java new file mode 100644 index 00000000..06681376 --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointTotalPageResponse.java @@ -0,0 +1,28 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import java.util.List; +import lombok.Builder; +import org.springframework.data.domain.Page; + +@Builder +public record PointTotalPageResponse( + int pageNum, + int pageSize, + int totalPages, + long totalElements, + boolean isLast, + List histories + +) { + + public static PointTotalPageResponse of(Page responsePage) { + return PointTotalPageResponse.builder() + .pageNum(responsePage.getNumber() + 1) + .pageSize(responsePage.getSize()) + .totalPages(responsePage.getTotalPages()) + .totalElements(responsePage.getTotalElements()) + .isLast(responsePage.isLast()) + .histories(responsePage.getContent()) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageCouponReceiptResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageCouponReceiptResponse.java new file mode 100644 index 00000000..5e7f458d --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageCouponReceiptResponse.java @@ -0,0 +1,21 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import lombok.Builder; + +@Builder +public record PointUsageCouponReceiptResponse( + String name, + int count, + int totalPrice +) { + + public static PointUsageCouponReceiptResponse of( + String name, int count, int totalPrice + ) { + return PointUsageCouponReceiptResponse.builder() + .name(name) + .count(count) + .totalPrice(totalPrice) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailReceiptResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailReceiptResponse.java new file mode 100644 index 00000000..f879e01e --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailReceiptResponse.java @@ -0,0 +1,21 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import java.util.List; +import lombok.Builder; + +@Builder +public record PointUsageDetailReceiptResponse( + String room, + List coupons + +) { + + public static PointUsageDetailReceiptResponse of( + String room, List coupons + ) { + return PointUsageDetailReceiptResponse.builder() + .room(room) + .coupons(coupons) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailResponse.java new file mode 100644 index 00000000..0892ce7d --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageDetailResponse.java @@ -0,0 +1,36 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; +import lombok.Builder; + +@Builder +public record PointUsageDetailResponse( + long id, + String category, + String type, + String status, + String name, + String description, + long trade, + long amount, + PointUsageReceiptResponse receipt + +) { + + public static PointUsageDetailResponse of( + PointUsage pointUsage, String description, + long trade, long amount, PointUsageReceiptResponse receipt + ) { + return PointUsageDetailResponse.builder() + .id(pointUsage.getId()) + .category("사용") + .type("쿠폰") + .status("구매 확정") + .name("할인 쿠폰 구매") + .description(description) + .trade(trade) + .amount(amount) + .receipt(receipt) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsagePageResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsagePageResponse.java new file mode 100644 index 00000000..c106f1c6 --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsagePageResponse.java @@ -0,0 +1,28 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import java.util.List; +import lombok.Builder; +import org.springframework.data.domain.Page; + +@Builder +public record PointUsagePageResponse( + int pageNum, + int pageSize, + int totalPages, + long totalElements, + boolean isLast, + List histories + +) { + + public static PointUsagePageResponse of(Page responsePage) { + return PointUsagePageResponse.builder() + .pageNum(responsePage.getNumber() + 1) + .pageSize(responsePage.getSize()) + .totalPages(responsePage.getTotalPages()) + .totalElements(responsePage.getTotalElements()) + .isLast(responsePage.isLast()) + .histories(responsePage.getContent()) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageReceiptResponse.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageReceiptResponse.java new file mode 100644 index 00000000..b89fa988 --- /dev/null +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/dto/response/PointUsageReceiptResponse.java @@ -0,0 +1,26 @@ +package com.backoffice.upjuyanolja.domain.point.dto.response; + +import java.util.List; +import lombok.Builder; + +@Builder +public record PointUsageReceiptResponse( + String orderId, + String tradeAt, + String accommodationName, + List orders + +) { + + public static PointUsageReceiptResponse of( + String orderId, String tradeAt, + String accommodationName, List orders + ) { + return PointUsageReceiptResponse.builder() + .orderId(orderId) + .tradeAt(tradeAt) + .accommodationName(accommodationName) + .orders(orders) + .build(); + } +} diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/Point.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/Point.java index dbbc519e..21d7f9d1 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/Point.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/Point.java @@ -1,6 +1,5 @@ package com.backoffice.upjuyanolja.domain.point.entity; -import com.backoffice.upjuyanolja.domain.coupon.exception.InsufficientPointsException; import com.backoffice.upjuyanolja.domain.member.entity.Member; import com.backoffice.upjuyanolja.global.common.entity.BaseTime; import jakarta.persistence.Column; @@ -51,11 +50,4 @@ public void updatePointBalance(long totalPointBalance) { this.totalPointBalance = totalPointBalance; } - public void decreasePointBalance(long usedPoints) { - if (totalPointBalance - usedPoints < 0) { - throw new InsufficientPointsException(); - } - totalPointBalance -= usedPoints; - } - } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointCharges.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointCharges.java index c91ef3bf..b4338064 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointCharges.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointCharges.java @@ -45,6 +45,10 @@ public class PointCharges extends BaseTime { @Comment("충전 포인트") private long chargePoint; + @Column(nullable = false) + @Comment("잔여 포인트") + private long remainPoint; + @Column(nullable = false) @Comment("충전 일시") private LocalDateTime chargeDate; @@ -70,6 +74,7 @@ public PointCharges( String paymentKey, String orderName, long chargePoint, + long remainPoint, LocalDateTime chargeDate, LocalDateTime endDate, boolean refundable, @@ -80,6 +85,7 @@ public PointCharges( this.paymentKey = paymentKey; this.orderName = orderName; this.chargePoint = chargePoint; + this.remainPoint = remainPoint; this.chargeDate = chargeDate; this.endDate = endDate; this.refundable = refundable; @@ -94,4 +100,9 @@ public void updateRefundable(boolean refundable) { this.refundable = refundable; } + public void updateRemainPoint(long remainPoint) { + this.remainPoint = remainPoint; + } + + } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointRefunds.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointRefunds.java index 1770fa78..88f4bf6e 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointRefunds.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointRefunds.java @@ -32,10 +32,9 @@ public class PointRefunds extends BaseTime { @Comment("환불 일시") private LocalDateTime refundDate; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "point_id") + @Column(nullable = false) @Comment("포인트 식별자") - private Point point; + private Long pointId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "point_charges_id") @@ -46,12 +45,12 @@ public class PointRefunds extends BaseTime { public PointRefunds( Long id, LocalDateTime refundDate, - Point point, + Long pointId, PointCharges pointCharges ) { this.id = id; this.refundDate = refundDate; - this.point = point; + this.pointId = pointId; this.pointCharges = pointCharges; } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointStatus.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointStatus.java index c9fe0368..d0eb66e2 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointStatus.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointStatus.java @@ -8,6 +8,7 @@ public enum PointStatus { USED("구매 확정"), + REMAINED("잔액 존재"), PAID("결제 완료"), CANCELED("취소 완료"); diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointUsage.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointUsage.java index becba448..8c6c479a 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointUsage.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/entity/PointUsage.java @@ -3,8 +3,6 @@ import com.backoffice.upjuyanolja.global.common.entity.BaseTime; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -28,10 +26,6 @@ public class PointUsage extends BaseTime { @Comment("포인트 사용 식별자") private Long id; - @Column(nullable = false) - @Comment("주문 이름") - private String orderName; - @Column(nullable = false) @Comment("주문 일시") private LocalDateTime orderDate; @@ -42,28 +36,18 @@ public class PointUsage extends BaseTime { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "point_id") - @Comment("포인트 식별자") private Point point; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "point_charges_id") - @Comment("포인트 충전 식별자") - private PointCharges pointCharges; - @Builder public PointUsage( Long id, - String orderName, LocalDateTime orderDate, long orderPrice, - Point point, - PointCharges pointcharges + Point point ) { this.id = id; - this.orderName = orderName; this.orderDate = orderDate; this.orderPrice = orderPrice; this.point = point; - this.pointCharges = pointcharges; } } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepository.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepository.java index 2df00706..319f05b3 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepository.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepository.java @@ -7,8 +7,10 @@ public interface PointChargesCustomRepository { - Long sumChargePointByRefundable(Point point); + Long sumTotalPaidPoint(Point point); - List findByPointAndRefundableAndRangeDate(Point point, + Long sumTotalRemainedPoint(Point point); + + List findByPointByStatusAndRangeDate(Point point, YearMonth rangeDate); } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepositoryImpl.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepositoryImpl.java index b3c0f918..3e158790 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepositoryImpl.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesCustomRepositoryImpl.java @@ -2,6 +2,7 @@ import com.backoffice.upjuyanolja.domain.point.entity.Point; import com.backoffice.upjuyanolja.domain.point.entity.PointCharges; +import com.backoffice.upjuyanolja.domain.point.entity.PointStatus; import com.backoffice.upjuyanolja.domain.point.entity.QPointCharges; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -18,34 +19,36 @@ public class PointChargesCustomRepositoryImpl implements PointChargesCustomRepos private final QPointCharges qPointCharges = QPointCharges.pointCharges; @Override - public Long sumChargePointByRefundable(Point point) { + public Long sumTotalPaidPoint(Point point){ return query.select(qPointCharges.chargePoint.sum()) .from(qPointCharges) - .where(isPointRefundable(point)) + .where(qPointCharges.pointStatus.eq(PointStatus.PAID)) .fetchFirst(); } @Override - public List findByPointAndRefundableAndRangeDate( - Point point, YearMonth rangeDate - ) { - List result = getPointCharges(point, rangeDate); - return result; + public Long sumTotalRemainedPoint(Point point){ + return query.select(qPointCharges.remainPoint.sum()) + .from(qPointCharges) + .where(qPointCharges.pointStatus.eq(PointStatus.REMAINED)) + .fetchFirst(); } - private List getPointCharges( + @Override + public List findByPointByStatusAndRangeDate( Point point, YearMonth rangeDate ) { return query.selectFrom(qPointCharges) - .where(isPointRefundable(point) + .where(isPointAvailableStatus(point) .and(eqChargeDate(rangeDate)) ) .fetch(); } - private BooleanExpression isPointRefundable(Point point) { + + private BooleanExpression isPointAvailableStatus(Point point) { return qPointCharges.point.eq(point) - .and(qPointCharges.refundable.isTrue()); + .and(qPointCharges.pointStatus.in(PointStatus.PAID, PointStatus.REMAINED)); } private BooleanExpression eqChargeDate(YearMonth rangeDate) { diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesRepository.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesRepository.java index 2ed9bc8a..acd130a0 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesRepository.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointChargesRepository.java @@ -1,6 +1,7 @@ package com.backoffice.upjuyanolja.domain.point.repository; import com.backoffice.upjuyanolja.domain.point.entity.PointCharges; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,6 +9,8 @@ public interface PointChargesRepository extends JpaRepository, PointChargesCustomRepository { - Page findByPointId(Long pointId, Pageable pageable); + Page findPageByPointId(Long pointId, Pageable pageable); + + List findByPointId(Long pointId); } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointRepository.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointRepository.java index f14470b0..d0485d7e 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointRepository.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointRepository.java @@ -1,14 +1,11 @@ package com.backoffice.upjuyanolja.domain.point.repository; -import com.backoffice.upjuyanolja.domain.member.entity.Member; import com.backoffice.upjuyanolja.domain.point.entity.Point; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PointRepository extends JpaRepository { - Optional findByMember(Member member); - Optional findByMemberId(Long memberId); } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointUsageRepository.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointUsageRepository.java index cbe4f5be..f1ab05f0 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointUsageRepository.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/repository/PointUsageRepository.java @@ -1,9 +1,13 @@ package com.backoffice.upjuyanolja.domain.point.repository; import com.backoffice.upjuyanolja.domain.point.entity.PointUsage; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface PointUsageRepository extends JpaRepository, PointUsageCustomRepository { + Page findPageByPointId(Long pointId, Pageable pageable); + } diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/point/service/PointService.java b/src/main/java/com/backoffice/upjuyanolja/domain/point/service/PointService.java index b4b20e80..3a0b0d1a 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/point/service/PointService.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/point/service/PointService.java @@ -1,6 +1,11 @@ package com.backoffice.upjuyanolja.domain.point.service; +import com.backoffice.upjuyanolja.domain.accommodation.service.AccommodationCommandService; +import com.backoffice.upjuyanolja.domain.coupon.entity.Coupon; +import com.backoffice.upjuyanolja.domain.coupon.entity.CouponIssuance; +import com.backoffice.upjuyanolja.domain.coupon.exception.InsufficientPointsException; +import com.backoffice.upjuyanolja.domain.coupon.service.CouponIssuanceGetService; import com.backoffice.upjuyanolja.domain.member.service.MemberGetService; import com.backoffice.upjuyanolja.domain.point.dto.request.PointChargeRequest; import com.backoffice.upjuyanolja.domain.point.dto.response.PointChargeDetailResponse; @@ -9,6 +14,13 @@ import com.backoffice.upjuyanolja.domain.point.dto.response.PointChargeResponse; import com.backoffice.upjuyanolja.domain.point.dto.response.PointSummaryResponse; import com.backoffice.upjuyanolja.domain.point.dto.response.PointTotalBalanceResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointTotalDetailResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointTotalPageResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsageCouponReceiptResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsageDetailReceiptResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsageDetailResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsagePageResponse; +import com.backoffice.upjuyanolja.domain.point.dto.response.PointUsageReceiptResponse; import com.backoffice.upjuyanolja.domain.point.dto.response.TossResponse; import com.backoffice.upjuyanolja.domain.point.entity.Point; import com.backoffice.upjuyanolja.domain.point.entity.PointCategory; @@ -25,19 +37,25 @@ import com.backoffice.upjuyanolja.domain.point.repository.PointRefundsRepository; import com.backoffice.upjuyanolja.domain.point.repository.PointRepository; import com.backoffice.upjuyanolja.domain.point.repository.PointUsageRepository; +import com.backoffice.upjuyanolja.domain.room.entity.Room; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.time.LocalDateTime; import java.time.YearMonth; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Base64; -import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -55,7 +73,11 @@ public class PointService { private final PointChargesRepository pointChargesRepository; private final PointRefundsRepository pointRefundsRepository; private final PointUsageRepository pointUsageRepository; + + private final AccommodationCommandService accommodationCommandService; + private final CouponIssuanceGetService couponIssuanceGetService; private final MemberGetService memberGetService; + private final ObjectMapper objectMapper; @Value("${point.toss.url}") @@ -81,12 +103,176 @@ public PointSummaryResponse getPointSummaryResponse(Long memberId, YearMonth ran } @Transactional(readOnly = true) - public PointTotalBalanceResponse getPointTotalBalanceResponse(Long memberId){ + public PointTotalBalanceResponse getPointTotalBalanceResponse(Long memberId) { Point memberPoint = getMemberPoint(memberId); return PointTotalBalanceResponse.of(memberPoint.getTotalPointBalance()); } + @Transactional(readOnly = true) + public PointChargePageResponse getPointChargePageResponse(Long memberId, Pageable pageable) { + Long pointId = getMemberPoint(memberId).getId(); + Page pointCharges = pointChargesRepository.findPageByPointId(pointId, + pageable); + + return PointChargePageResponse.of(new PageImpl<>( + pointCharges.stream() + .map(pointCharge -> PointChargeDetailResponse.of( + pointCharge, getPointChargeCategoryAndType(pointCharge).get(0), + getPointChargeCategoryAndType(pointCharge).get(1), + getPointChargeReceiptResponse(pointCharge) + ) + ) + .toList(), + pageable, + pointCharges.getTotalElements() + ) + ); + } + + @Transactional(readOnly = true) + public PointChargeResponse getDetailChargePointResponse(Long chargeId) { + PointCharges detailchargePoint = pointChargesRepository.findById(chargeId) + .orElseThrow(PointNotFoundException::new); + + return PointChargeResponse.of(detailchargePoint); + } + + @Transactional(readOnly = true) + public PointUsagePageResponse getPointUsagePageResponse(Long memberId, Pageable pageable) { + Long pointId = getMemberPoint(memberId).getId(); + Page pointUsages = pointUsageRepository.findPageByPointId(pointId, + pageable); + + return PointUsagePageResponse.of(new PageImpl<>( + pointUsages.stream() + .map(pointUsage -> { + List couponIssuances = + couponIssuanceGetService.getCouponIssuanceByPointUsage(pointUsage); + if (couponIssuances.isEmpty()) { + return PointUsageDetailResponse.builder().build(); + } + Map> couponIssuancesMap = + createCouponIssuancesMap(couponIssuances); + CouponIssuance selectCouponIssuance = couponIssuances.get(0); + String accommodationName = + accommodationCommandService + .findAccommodationByRoomId(selectCouponIssuance.getRoom().getId()) + .getName(); + + return PointUsageDetailResponse.of( + pointUsage, + getPointUsageDescription( + selectCouponIssuance, couponIssuancesMap.keySet().size(), + accommodationName + ), + getPointUsageTrade(couponIssuances), + pointUsage.getOrderPrice(), + getPointUsageReceiptResponse( + pointUsage, accommodationName, + couponIssuancesMap, couponIssuances + ) + ); + } + ) + .toList(), + pageable, + pointUsages.getTotalElements() + ) + ); + } + + @Transactional(readOnly = true) + public PointTotalPageResponse getTotalPointPageResponse(Long memberId, Pageable pageable) { + Long pointId = getMemberPoint(memberId).getId(); + PointChargePageResponse chargePageResponse = getPointChargePageResponse(memberId, + pageable); + PointUsagePageResponse usagePageResponse = getPointUsagePageResponse(memberId, pageable); + List pointTotalDetailResponses = new ArrayList<>(); + List result = new ArrayList<>(); + long id = 1; + + for (PointChargeDetailResponse charge : chargePageResponse.histories()) { + pointTotalDetailResponses.add(PointTotalDetailResponse.of( + charge.category(), + charge.type(), + charge.status(), + charge.name(), + "", + charge.trade(), + charge.amount(), + charge.receipt().tradeAt(), + charge.receipt() + )); + } + for (PointUsageDetailResponse usage : usagePageResponse.histories()) { + pointTotalDetailResponses.add(PointTotalDetailResponse.of( + usage.category(), + usage.type(), + usage.status(), + usage.name(), + usage.description(), + usage.trade(), + usage.amount(), + usage.receipt().tradeAt(), + usage.receipt() + )); + } + pointTotalDetailResponses.sort(Comparator.comparing(PointTotalDetailResponse::date)); + for (PointTotalDetailResponse response : pointTotalDetailResponses) { + result.add(PointTotalDetailResponse.from(id++, response)); + } + + return PointTotalPageResponse.of( + new PageImpl<>( + result, + pageable, + result.size() + ) + ); + } + + public PointChargeResponse chargePoint(Long memberId, PointChargeRequest request) { + Point memberPoint = getMemberPoint(memberId); + TossResponse tossResponse = getTossChargeResponse(request); + + validatePointChargeRequest(request, tossResponse); + PointCharges chargePoint = createPointCharge(memberPoint, tossResponse); + updateTotalPointBalance(memberPoint); + + return PointChargeResponse.of(chargePoint); + } + + public void refundPoint(Long chargeId) { + PointCharges pointCharges = pointChargesRepository.findById(chargeId) + .orElseThrow(PointNotFoundException::new); + TossResponse tossResponse = getTossRefundResponse(pointCharges.getPaymentKey()); + + validatePointRefund(pointCharges); + updateChargePointStatus(pointCharges, PointStatus.CANCELED); + updateTotalPointBalance(pointCharges.getPoint()); + createPointRefund(pointCharges, tossResponse); + + } + + @Transactional(readOnly = true) + public void validatePoint(final Long memberId, final long requestPointBalance) { + Point memberPoint = getMemberPoint(memberId); + + if (memberPoint.getTotalPointBalance() < requestPointBalance) { + throw new InsufficientPointsException(); + } + } + + public PointUsage usePointForCoupon(final Long memberId, final long totalPrice) { + Point memberPoint = getMemberPoint(memberId); + PointUsage resultPointUsage = createPointUsage(memberPoint, totalPrice); + + useChargePointForCoupon(totalPrice, memberPoint.getId(), memberPoint); + + return resultPointUsage; + } + private Point getMemberPoint(Long memberId) { return pointRepository.findByMemberId(memberId) .orElseGet(() -> createPoint(memberId)); @@ -103,9 +289,56 @@ private Point createPoint(Long memberId) { return newPoint; } + private PointCharges createPointCharge( + Point point, TossResponse tossResponse + ) { + + PointCharges pointCharges = PointCharges.builder() + .point(point) + .pointStatus(PointStatus.PAID) + .paymentKey(tossResponse.paymentKey()) + .orderName(tossResponse.orderId()) + .chargePoint(tossResponse.totalAmount()) + .remainPoint(0L) + .chargeDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime()) + .endDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime().plusDays(7)) + .refundable(true) + .build(); + + pointChargesRepository.save(pointCharges); + + return pointCharges; + } + + private PointRefunds createPointRefund(PointCharges pointCharges, TossResponse tossResponse) { + + PointRefunds pointRefunds = PointRefunds.builder() + .pointId(pointCharges.getPoint().getId()) + .pointCharges(pointCharges) + .refundDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime()) + .build(); + + pointRefundsRepository.save(pointRefunds); + + return pointRefunds; + } + + private PointUsage createPointUsage( + Point point, long orderPrice + ) { + PointUsage pointUsage = PointUsage.builder() + .point(point) + .orderPrice(orderPrice) + .orderDate(LocalDateTime.now()) + .build(); + + pointUsageRepository.save(pointUsage); + + return pointUsage; + } private long getTotalChargePoint(Point point, YearMonth rangeDate) { - return pointChargesRepository.findByPointAndRefundableAndRangeDate( + return pointChargesRepository.findByPointByStatusAndRangeDate( point, rangeDate ).stream() .mapToLong(PointCharges::getChargePoint) @@ -120,66 +353,75 @@ private long getTotalUsePoint(Point point, YearMonth rangeDate) { .sum(); } - @Transactional(readOnly = true) - public PointChargeResponse getDetailChargePoint(Long chargeId) { - PointCharges detailchargePoint = pointChargesRepository.findById(chargeId) - .orElseThrow(PointNotFoundException::new); - - return PointChargeResponse.of(detailchargePoint); + private void updateTotalPointBalance(Point point) { + long totalPoint = + Optional.ofNullable(pointChargesRepository.sumTotalPaidPoint(point)).orElse(0L) + + Optional.ofNullable(pointChargesRepository.sumTotalRemainedPoint(point)).orElse(0L); + point.updatePointBalance(totalPoint); + pointRepository.save(point); } - public PointChargePageResponse getChargePoints(Long memberId, Pageable pageable) { - Long pointId = getMemberPoint(memberId).getId(); - Page pointCharges = pointChargesRepository.findByPointId(pointId, pageable); + private void updateChargePointStatus(PointCharges pointCharges, PointStatus pointStatus) { + pointCharges.updatePointStatus(pointStatus); + pointCharges.updateRefundable(false); + } - return PointChargePageResponse.of(new PageImpl<>( - pointCharges.stream() - .map(pointCharge -> PointChargeDetailResponse.of( - pointCharge, getPointChargeCategoryAndType(pointCharge).get(0), - getPointChargeCategoryAndType(pointCharge).get(1), - getPointChargeReceiptResponse(pointCharge)) - ) - .toList(), - pageable, - pointCharges.getTotalElements() - ) - ); + private void updateChargeRemainPoint(PointCharges pointCharges, long totalPrice) { + long remainPoint = pointCharges.getChargePoint() - totalPrice; + pointCharges.updateRemainPoint(remainPoint); + pointChargesRepository.save(pointCharges); } - private List getPointChargeCategoryAndType(PointCharges pointCharges) { - List results = new ArrayList<>(); + private void useChargePointForCoupon(long totalPrice, Long pointChargeId, Point memberPoint) { + List pointCharges = + pointChargesRepository.findByPointId(pointChargeId); + long resultPoint = memberPoint.getTotalPointBalance(); + + for (PointCharges pointCharge : pointCharges) { + if (resultPoint >= totalPrice) { + break; + } + + switch (pointCharge.getPointStatus()) { + case PAID: + resultPoint += pointCharge.getChargePoint(); + if (pointCharge.getChargePoint() > totalPrice) { + updateChargePointStatus(pointCharge, PointStatus.REMAINED); + updateChargeRemainPoint(pointCharge, totalPrice); + } + updateChargePointStatus(pointCharge, PointStatus.USED); + break; + case REMAINED: + resultPoint += pointCharge.getRemainPoint(); + if (totalPrice >= resultPoint) { + updateChargePointStatus(pointCharge, PointStatus.USED); + updateChargeRemainPoint(pointCharge, pointCharge.getRemainPoint()); + } + updateChargeRemainPoint(pointCharge, (resultPoint - totalPrice)); + break; + } - switch (pointCharges.getPointStatus()) { - case PAID: - results.add(PointCategory.CHARGE.getDescription()); - results.add(PointType.POINT.getDescription()); - case CANCELED: - results.add(PointCategory.REFUND.getDescription()); - results.add(PointType.REFUND.getDescription()); - case USED: - results.add(PointCategory.USE.getDescription()); - results.add(PointType.POINT.getDescription()); } - return results; - + updateTotalPointBalance(memberPoint); } private PointChargeReceiptResponse getPointChargeReceiptResponse( PointCharges pointCharges) { switch (pointCharges.getPointStatus()) { - case PAID : - case USED : + case PAID: + case USED: + case REMAINED: return PointChargeReceiptResponse.of( pointCharges.getOrderName(), - pointCharges.getChargeDate().toString(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(pointCharges.getChargeDate()), pointCharges.getChargePoint() ); case CANCELED: PointRefunds pointRefund = pointRefundsRepository.findByPointCharges(pointCharges); return PointChargeReceiptResponse.of( pointCharges.getOrderName(), - pointRefund.getRefundDate().toString(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(pointRefund.getRefundDate()), pointCharges.getChargePoint() ); default: @@ -188,39 +430,111 @@ private PointChargeReceiptResponse getPointChargeReceiptResponse( } + private List getPointChargeCategoryAndType(PointCharges pointCharges) { + List results = new ArrayList<>(); - public PointChargeResponse getChargePointResponse(Long memberId, PointChargeRequest request) { - Point memberPoint = getMemberPoint(memberId); - TossResponse tossResponse = getTossChargeResponse(request); + switch (pointCharges.getPointStatus()) { + case PAID: + results.add(PointCategory.CHARGE.getDescription()); + results.add(PointType.POINT.getDescription()); + break; + case CANCELED: + results.add(PointCategory.REFUND.getDescription()); + results.add(PointType.REFUND.getDescription()); + break; + case USED: + results.add(PointCategory.USE.getDescription()); + results.add(PointType.POINT.getDescription()); + break; + case REMAINED: + results.add(PointCategory.CHARGE.getDescription()); + results.add(PointType.POINT.getDescription()); + break; + } + return results; - validatePointChargeRequest(request, tossResponse); - PointCharges chargePoint = createPointCharge(memberPoint, tossResponse); - pointChargesRepository.save(chargePoint); - updateTotalPoint(memberPoint); + } - return PointChargeResponse.of(chargePoint); + private Map> createCouponIssuancesMap( + List couponIssuances + ) { + return couponIssuances.stream() + .collect(Collectors.groupingBy( + couponIssuance -> couponIssuance.getRoom(), + Collectors.mapping( + couponIssuance -> couponIssuance.getCoupon(), + Collectors.toList() + ) + )); } - private PointCharges createPointCharge( - Point point, TossResponse tossResponse + private String getPointUsageDescription( + CouponIssuance couponIssuance, int size, String accommodationName ) { - return PointCharges.builder() - .point(point) - .pointStatus(PointStatus.PAID) - .paymentKey(tossResponse.paymentKey()) - .orderName(tossResponse.orderId()) - .chargePoint(tossResponse.totalAmount()) - .chargeDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime()) - .endDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime().plusDays(7)) - .refundable(true) - .build(); + StringBuilder stringBuilder = new StringBuilder(); + + if (size > 2) { + stringBuilder.append( + couponIssuance.getRoom().getName() + " 외 " + String.valueOf(size - 1) + "건" + ); + + } else { + stringBuilder.append(couponIssuance.getRoom().getName()); + } + + stringBuilder.append(" | " + accommodationName); + + return stringBuilder.toString(); } - private void updateTotalPoint(Point point) { - long chargeSum = Optional.of(pointChargesRepository.sumChargePointByRefundable(point)) - .orElse(0L); - point.updatePointBalance(chargeSum); - pointRepository.save(point); + private long getPointUsageTrade(List couponIssuancesMap) { + return couponIssuancesMap.stream() + .mapToLong(CouponIssuance::getQuantity) + .sum(); + } + + private PointUsageReceiptResponse getPointUsageReceiptResponse( + PointUsage pointUsages, String accommodationName, + Map> couponIssuancesMap, List couponIssuances + ) { + return PointUsageReceiptResponse.of( + getPointUsageReceiptOrderId(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").format(pointUsages.getOrderDate()), + accommodationName, + getPointUsageDetailReceiptResponse(couponIssuancesMap, couponIssuances) + ); + } + + private String getPointUsageReceiptOrderId() { + return "O-" + ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE); + } + + private List getPointUsageDetailReceiptResponse( + Map> couponIssuanceMaps, List couponIssuances + ) { + return couponIssuanceMaps.entrySet().stream() + .map(entry -> PointUsageDetailReceiptResponse.of( + entry.getKey().getName(), + getPointUsageCouponReceiptResponse(entry.getKey(), couponIssuances) + ) + ) + .toList(); + } + + private List getPointUsageCouponReceiptResponse( + Room room, List couponIssuances) { + return couponIssuances.stream() + .filter(couponIssuance -> couponIssuance.getRoom().equals(room)) + .map(couponIssuance -> { + Coupon coupon = couponIssuance.getCoupon(); + return PointUsageCouponReceiptResponse.of( + coupon.getDiscount() + "원 쿠폰 | " + coupon.getDiscount() / 10 + "p", + couponIssuance.getQuantity(), + couponIssuance.getAmount() + ); + } + ) + .toList(); } private String createTossAuthorizations() { @@ -262,37 +576,13 @@ private TossResponse getTossChargeResponse(PointChargeRequest request) { } - private void validatePointChargeRequest( - PointChargeRequest request, TossResponse tossResponse - ) { - if (!request.orderId().equals(tossResponse.orderId()) || - !request.paymentKey().equals(tossResponse.paymentKey()) || - request.amount() != tossResponse.totalAmount()) { - throw new PaymentAuthorizationFailedException(); - } - } - - public void refundPoint(Long chargeId) { - PointCharges pointCharges = pointChargesRepository.findById(chargeId) - .orElseThrow(PointNotFoundException::new); - - validatePointRefund(pointCharges); - - TossResponse tossResponse = getTossRefundResponse(pointCharges.getPaymentKey()); - updateChargePointStatus(pointCharges); - updateTotalPoint(pointCharges.getPoint()); - - PointRefunds pointRefunds = createPointRefund(pointCharges, tossResponse); - pointRefundsRepository.save(pointRefunds); - } - private TossResponse getTossRefundResponse(String paymentKey) { HttpResponse response; try { HttpRequest httpRequest = HttpRequest.newBuilder() - .uri(URI.create(tossBaseUrl +paymentKey+ "/cancel")) + .uri(URI.create(tossBaseUrl + paymentKey + "/cancel")) .header("Authorization", "Basic " + createTossAuthorizations()) .header("Content-Type", "application/json") .method("POST", HttpRequest.BodyPublishers.ofString( @@ -310,17 +600,14 @@ private TossResponse getTossRefundResponse(String paymentKey) { } - private void updateChargePointStatus(PointCharges pointCharges) { - pointCharges.updatePointStatus(PointStatus.CANCELED); - pointCharges.updateRefundable(false); - } - - private PointRefunds createPointRefund(PointCharges pointCharges, TossResponse tossResponse) { - return PointRefunds.builder() - .point(pointCharges.getPoint()) - .pointCharges(pointCharges) - .refundDate(ZonedDateTime.parse(tossResponse.approvedAt()).toLocalDateTime()) - .build(); + private void validatePointChargeRequest( + PointChargeRequest request, TossResponse tossResponse + ) { + if (!request.orderId().equals(tossResponse.orderId()) || + !request.paymentKey().equals(tossResponse.paymentKey()) || + request.amount() != tossResponse.totalAmount()) { + throw new PaymentAuthorizationFailedException(); + } } private void validatePointRefund(PointCharges pointCharges) { diff --git a/src/main/java/com/backoffice/upjuyanolja/domain/reservation/service/ReservationService.java b/src/main/java/com/backoffice/upjuyanolja/domain/reservation/service/ReservationService.java index 94e4f6c8..e63f861a 100644 --- a/src/main/java/com/backoffice/upjuyanolja/domain/reservation/service/ReservationService.java +++ b/src/main/java/com/backoffice/upjuyanolja/domain/reservation/service/ReservationService.java @@ -68,13 +68,6 @@ public void create(Member currentMember, CreateReservationRequest request) { List roomStocks = getRoomStock(room, request.getStartDate(), request.getEndDate()); - /* - * 객실 재고 수정 - * */ - for (RoomStock roomStock : roomStocks) { - stockService.decreaseRoomStock(roomStock.getId()); //lock - } - /* * 쿠폰 유효성 및 재고 검증 * request.getCouponId() = null 인 경우 스킵 @@ -89,6 +82,17 @@ public void create(Member currentMember, CreateReservationRequest request) { } } + // 할인 금액 계산 + int totalAmount = getValidTotalAmount(request.getTotalPrice(), + room.getPrice().getOffWeekDaysMinFee(), coupon); + + /* + * 객실 재고 수정 + * */ + for (RoomStock roomStock : roomStocks) { + stockService.decreaseRoomStock(roomStock.getId()); //lock + } + /* * 쿠폰 재고 수정 * */ @@ -96,10 +100,6 @@ public void create(Member currentMember, CreateReservationRequest request) { stockService.decreaseCouponStock(coupon.getId()); //lock } - // 할인 금액 계산 - int totalAmount = getValidTotalAmount(request.getTotalPrice(), - room.getPrice().getOffWeekDaysMinFee(), coupon); - /* * 예약 및 결제 저장 * */ diff --git a/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotal.http b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalBalance.http similarity index 86% rename from src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotal.http rename to src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalBalance.http index f07902e6..8c20a816 100644 --- a/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotal.http +++ b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalBalance.http @@ -13,6 +13,6 @@ Content-Type: application/json ### -GET http://localhost:8080/api/points/total +GET http://localhost:8080/api/points/total-balance Accept: application/json Authorization: Bearer {{access_token}} \ No newline at end of file diff --git a/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalPoint.http b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalPoint.http new file mode 100644 index 00000000..02612a81 --- /dev/null +++ b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getTotalPoint.http @@ -0,0 +1,18 @@ +POST http://localhost:8080/api/auth/owners/signin +Content-Type: application/json + +{ + "email": "{{email}}", + "password": "{{password}}" +} + +> {% + client.log(response.body.accessToken); + client.global.set("access_token",response.body.accessToken) +%} + +### + +GET http://localhost:8080/api/points/total +Accept: application/json +Authorization: Bearer {{access_token}} \ No newline at end of file diff --git a/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getUsagePoint.http b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getUsagePoint.http new file mode 100644 index 00000000..6770cd8a --- /dev/null +++ b/src/test/java/com/backoffice/upjuyanolja/httpTest/point/select/point-getUsagePoint.http @@ -0,0 +1,18 @@ +POST http://localhost:8080/api/auth/owners/signin +Content-Type: application/json + +{ + "email": "{{email}}", + "password": "{{password}}" +} + +> {% + client.log(response.body.accessToken); + client.global.set("access_token",response.body.accessToken) +%} + +### + +GET http://localhost:8080/api/points/usages +Accept: application/json +Authorization: Bearer {{access_token}} \ No newline at end of file