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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ public void updateParkingLotImages(AuthUser authUser, Long parkingLotId, Parking
}

// 주차장 삭제
@Transactional
public void deleteParkingLot(AuthUser authUser, Long parkingLotId) {
Long userId = authUser.getId();
ParkingLot parkingLot = parkingLotReader.getOwnedParkingLot(userId, parkingLotId);
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/parkez/queue/web/QueueController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.parkez.queue.dto.response.MyWaitingQueueDetailResponse;
import com.parkez.queue.dto.response.MyWaitingQueueListResponse;
import com.parkez.queue.service.QueueService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -16,17 +17,19 @@
@RequestMapping("/api/waiting")
@RestController
@RequiredArgsConstructor
@Tag(name = "16. 대기열 API", description = "대기열 API")
@Tag(name = "18. 대기열 API", description = "대기열 API")
public class QueueController {
private final QueueService queueService;

@GetMapping("/me")
@Operation(summary = "나의 전체 대기열 조회", description = "내가 대기 중인 모든 대기열을 조회합니다.")
public Response<List<MyWaitingQueueListResponse>> getMyWaitingQueues(@AuthenticatedUser @Parameter(hidden = true) AuthUser authUser) {
List<MyWaitingQueueListResponse> response = queueService.findMyWaitingQueues(authUser);
return Response.of(response);
}

@GetMapping("/{reservationId}")
@Operation(summary = "예약에 대한 나의 대기열 조회", description = "해당 예약에 대한 나의 대기열 정보를 상세 조회합니다.")
public Response<MyWaitingQueueDetailResponse> getMyQueue(
@AuthenticatedUser @Parameter(hidden = true) AuthUser authUser,
@PathVariable Long reservationId) {
Expand All @@ -35,6 +38,7 @@ public Response<MyWaitingQueueDetailResponse> getMyQueue(
}

@DeleteMapping("/{reservationId}")
@Operation(summary = "예약에 대한 나의 대기열 취소", description = "해당 예약에 대한 나의 대기열을 취소합니다.")
public Response<Void> cancelMyQueue(
@AuthenticatedUser @Parameter(hidden = true) AuthUser authUser,
@PathVariable Long reservationId) {
Expand Down
134 changes: 134 additions & 0 deletions src/main/java/com/parkez/reservation/service/ReservationProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.parkez.reservation.service;

import com.parkez.common.exception.ParkingEasyException;
import com.parkez.common.principal.AuthUser;
import com.parkez.parkingzone.domain.entity.ParkingZone;
import com.parkez.parkingzone.domain.enums.ParkingZoneStatus;
import com.parkez.parkingzone.service.ParkingZoneReader;
import com.parkez.promotion.domain.entity.Coupon;
import com.parkez.promotion.domain.entity.PromotionIssue;
import com.parkez.promotion.excption.PromotionIssueErrorCode;
import com.parkez.promotion.service.PromotionIssueReader;
import com.parkez.promotion.service.PromotionIssueValidator;
import com.parkez.promotion.service.PromotionIssueWriter;
import com.parkez.queue.domain.enums.JoinQueueResult;
import com.parkez.queue.exception.QueueErrorCode;
import com.parkez.queue.service.QueueService;
import com.parkez.reservation.domain.entity.Reservation;
import com.parkez.reservation.domain.enums.ReservationStatus;
import com.parkez.reservation.dto.request.ReservationRequest;
import com.parkez.reservation.dto.response.ReservationResponse;
import com.parkez.reservation.exception.ReservationErrorCode;
import com.parkez.user.domain.entity.User;
import com.parkez.user.service.UserReader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

@Component
@RequiredArgsConstructor
public class ReservationProcessor {

private final ReservationReader reservationReader;
private final ReservationWriter reservationWriter;
private final UserReader userReader;
private final ParkingZoneReader parkingZoneReader;
private final PromotionIssueReader promotionIssueReader;
private final PromotionIssueValidator promotionIssueValidator;
private final PromotionIssueWriter promotionIssueWriter;
private final QueueService queueService;

@Transactional
public ReservationResponse create(AuthUser authUser, ReservationRequest request, LocalDateTime now) {
User user = userReader.getActiveUserById(authUser.getId());
ParkingZone parkingZone = parkingZoneReader.getActiveByParkingZoneId(request.getParkingZoneId());

if (!validateRequestTime(request)) {
throw new ParkingEasyException(ReservationErrorCode.NOT_VALID_REQUEST_TIME);
}

if (!parkingZone.getStatus().equals(ParkingZoneStatus.AVAILABLE)) {
throw new ParkingEasyException(ReservationErrorCode.CANT_RESERVE_UNAVAILABLE_PARKING_ZONE);
}

if (!parkingZone.isOpened(request.getStartDateTime(), request.getEndDateTime())) {
throw new ParkingEasyException(ReservationErrorCode.CANT_RESERVE_AT_CLOSE_TIME);
}

List<ReservationStatus> statusList = List.of(ReservationStatus.PENDING, ReservationStatus.CONFIRMED);

if (reservationReader.existsReservationByConditionsForUser(parkingZone, request.getStartDateTime(), request.getEndDateTime(), user.getId(), statusList)) {
throw new ParkingEasyException(ReservationErrorCode.ALREADY_RESERVED_BY_YOURSELF);
}

boolean existed = reservationReader.existsReservationByConditions(parkingZone, request.getStartDateTime(), request.getEndDateTime(), statusList);

if (existed) {
handleJoinQueue(user, request);
return null;
}

long hours = calculateUsedHour(request.getStartDateTime(), request.getEndDateTime());

BigDecimal originalPrice = parkingZone.getParkingLotPricePerHour().multiply(BigDecimal.valueOf(hours));
BigDecimal discountAmount = BigDecimal.ZERO;

Long promotionIssueId = null;
if (request.getPromotionIssueId() != null) {
PromotionIssue promotionIssue = promotionIssueReader.getWithPromotionAndCouponById(request.getPromotionIssueId());

if (!promotionIssue.isOwnedBy(authUser.getId())) {
throw new ParkingEasyException(PromotionIssueErrorCode.NOT_YOUR_COUPON);
}

promotionIssueId = promotionIssue.getId();
promotionIssueValidator.validateCanBeUsed(promotionIssue, now);
Coupon coupon = promotionIssue.getCoupon();
discountAmount = coupon.calculateDiscount(originalPrice);

promotionIssueWriter.use(promotionIssue, now);
}

BigDecimal finalPrice = originalPrice.subtract(discountAmount);

Reservation reservation = reservationWriter.create(
user,
parkingZone,
request.getStartDateTime(),
request.getEndDateTime(),
originalPrice,
discountAmount,
finalPrice,
promotionIssueId
);

return ReservationResponse.from(reservation);
}

private boolean validateRequestTime(ReservationRequest request) {
LocalDateTime startDateTime = request.getStartDateTime();
LocalDateTime endDateTime = request.getEndDateTime();

return startDateTime.isBefore(endDateTime)
&& startDateTime.isAfter(LocalDateTime.now())
&& startDateTime.toLocalDate().equals(endDateTime.toLocalDate());
}

private long calculateUsedHour(LocalDateTime startDateTime, LocalDateTime endDateTime) {
return ChronoUnit.HOURS.between(startDateTime, endDateTime);
}

private void handleJoinQueue(User user, ReservationRequest request) {
JoinQueueResult result = queueService.joinWaitingQueue(user.getId(), request);

if (result == JoinQueueResult.ALREADY_JOINED) {
throw new ParkingEasyException(QueueErrorCode.ALREADY_IN_QUEUE);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.math.BigDecimal;
import java.time.LocalDateTime;
Expand All @@ -58,80 +59,14 @@ public class ReservationService {
private final PromotionIssueValidator promotionIssueValidator;
private final PromotionIssueWriter promotionIssueWriter;
private final QueueService queueService;
private final ReservationProcessor reservationProcessor;

private static final long CANCEL_LIMIT_HOURS = 1L;
private static final long EXPIRATION_TIME = 10L;

@Transactional
public ReservationResponse createReservation(AuthUser authUser, ReservationRequest request, LocalDateTime now) {
try {
return distributedLockManager.executeWithLock(request.getParkingZoneId(), () -> {

User user = userReader.getActiveUserById(authUser.getId());
ParkingZone parkingZone = parkingZoneReader.getActiveByParkingZoneId(request.getParkingZoneId());

if (!validateRequestTime(request)) {
throw new ParkingEasyException(ReservationErrorCode.NOT_VALID_REQUEST_TIME);
}

if (!parkingZone.getStatus().equals(ParkingZoneStatus.AVAILABLE)) {
throw new ParkingEasyException(ReservationErrorCode.CANT_RESERVE_UNAVAILABLE_PARKING_ZONE);
}

if (!parkingZone.isOpened(request.getStartDateTime(), request.getEndDateTime())) {
throw new ParkingEasyException(ReservationErrorCode.CANT_RESERVE_AT_CLOSE_TIME);
}

List<ReservationStatus> statusList = List.of(ReservationStatus.PENDING, ReservationStatus.CONFIRMED);

if (reservationReader.existsReservationByConditionsForUser(parkingZone, request.getStartDateTime(), request.getEndDateTime(), user.getId(), statusList)) {
throw new ParkingEasyException(ReservationErrorCode.ALREADY_RESERVED_BY_YOURSELF);
}

boolean existed = reservationReader.existsReservationByConditions(parkingZone, request.getStartDateTime(), request.getEndDateTime(), statusList);

if (existed) {
handleJoinQueue(user, request);
return null;
}

long hours = calculateUsedHour(request.getStartDateTime(), request.getEndDateTime());

BigDecimal originalPrice = parkingZone.getParkingLotPricePerHour().multiply(BigDecimal.valueOf(hours));
BigDecimal discountAmount = BigDecimal.ZERO;

Long promotionIssueId = null;
if (request.getPromotionIssueId() != null) {
PromotionIssue promotionIssue = promotionIssueReader.getWithPromotionAndCouponById(request.getPromotionIssueId());

if (!promotionIssue.isOwnedBy(authUser.getId())) {
throw new ParkingEasyException(PromotionIssueErrorCode.NOT_YOUR_COUPON);
}

promotionIssueId = promotionIssue.getId();
promotionIssueValidator.validateCanBeUsed(promotionIssue, now);
Coupon coupon = promotionIssue.getCoupon();
discountAmount = coupon.calculateDiscount(originalPrice);

promotionIssueWriter.use(promotionIssue, now);
}

BigDecimal finalPrice = originalPrice.subtract(discountAmount);

Reservation reservation = reservationWriter.create(
user,
parkingZone,
request.getStartDateTime(),
request.getEndDateTime(),
originalPrice,
discountAmount,
finalPrice,
promotionIssueId
);

return ReservationResponse.from(reservation);
});

return distributedLockManager.executeWithLock(request.getParkingZoneId(), () -> reservationProcessor.create(authUser, request, now));
} catch (ParkingEasyException e) {
if (e.getErrorCode() == ReservationErrorCode.RESERVATION_LOCK_FAILED) {
// 락 선점 실패 → 대기열 등록은 완료했으므로 예약 생성 결과 데이터는 null 반환
Expand All @@ -142,7 +77,6 @@ public ReservationResponse createReservation(AuthUser authUser, ReservationReque
}
}


public Page<ReservationResponse> getMyReservations(AuthUser authUser, int page, int size) {

int adjustedPage = page - 1;
Expand Down Expand Up @@ -180,6 +114,7 @@ public Page<ReservationResponse> getOwnerReservations(AuthUser authUser, Long pa
return pageReservations.map(ReservationResponse::from);
}

@Transactional
public void completeReservation(AuthUser authUser, Long reservationId) {

Reservation reservation = reservationReader.findMyReservation(authUser.getId(), reservationId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ private SettlementAmounts calculateSettlementAmounts(List<Payment> payments) {
return new SettlementAmounts(totalAmount, totalFee, netAmount);
}

@Transactional
public void completeSettlement(Long settlementId) {

Settlement settlement = settlementReader.getById(settlementId);
Expand Down
Loading
Loading