통계 및 부가 기능 | 매장별 예약 통계 API 구현

Faithful Dev·2025년 3월 17일

매장 예약 서비스

목록 보기
14/15

구현한 기능

  • 매장별 예약 통계 API 구현
  • 기간별, 시간대별, 상태별 예약 통계 제공
  • 파트너(점장)가 매장 운영 데이터를 분석할 수 있는 통계 정보 제공

코드 스냅샷

기간별 예약 통계 API

@GetMapping("/period/stores/{storeId}/partners/{partnerId}")
@PreAuthorize("hasRole('ROLE_PARTNER')")
public ResponseEntity<ApiResponse<ReservationStatsDto.PeriodStatsResponse>> getPeriodStats(
        @PathVariable Long storeId,
        @PathVariable Long partnerId,
        @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
        @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate
) {
    validateStoreOwnership(storeId, partnerId);
    
    ReservationStatsDto.PeriodStatsResponse response = 
            reservationStatsService.getPeriodStats(storeId, startDate, endDate);
    
    return ResponseEntity.ok(ApiResponse.success("기간별 예약 통계를 성공적으로 조회했습니다.", response));
}

시간대별 예약 통계 API

// 서비스 코드 스냅샷
@Transactional(readOnly = true)
public ReservationStatsDto.TimeSlotStatsResponse getTimeSlotStats(Long storeId, LocalDate startDate, LocalDate endDate) {
    validateDateRange(startDate, endDate);
    
    Store store = getStoreById(storeId);
    List<Reservation> reservations = getReservationsForPeriod(storeId, startDate, endDate);
    
    // 시간대별 예약 분포
    Map<String, Long> timeSlotDistribution = reservations.stream()
            .collect(Collectors.groupingBy(
                    reservation -> reservation.getReservationTime().format(DateTimeFormatter.ofPattern("HH:mm")),
                    Collectors.counting()
            ));
    
    // 인기 시간대 계산 (상위 5개)
    List<ReservationStatsDto.TimeSlotData> mostPopularTimeSlots = timeSlotDistribution.entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(5)
            .map(entry -> ReservationStatsDto.TimeSlotData.builder()
                    .timeSlot(LocalTime.parse(entry.getKey(), DateTimeFormatter.ofPattern("HH:mm")))
                    .count(entry.getValue())
                    .build())
            .collect(Collectors.toList());
    
    return ReservationStatsDto.TimeSlotStatsResponse.builder()
            .storeId(storeId)
            .storeName(store.getName())
            .startDate(startDate)
            .endDate(endDate)
            .timeSlotDistribution(timeSlotDistribution)
            .mostPopularTimeSlots(mostPopularTimeSlots)
            .build();
}

상태별 예약 통계 API

private double calculateRate(List<Reservation> reservations, long totalReservations, ReservationStatus... statuses) {
    if (totalReservations == 0) {
        return 0.0;
    }
    
    long count = countByStatus(reservations, statuses);
    return (double) count / totalReservations * 100;
}

보안 및 권한 관리

private void validateStoreOwnership(Long storeId, Long partnerId) {
    // 파트너 소유권 검증
    authenticationUtil.validatePartnerOwnership(partnerId);
    
    // 매장 소유권 검증
    Store store = storeRepository.findById(storeId)
            .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
    
    if (!store.getPartner().getId().equals(partnerId)) {
        throw new CustomException(ErrorCode.FORBIDDEN, "해당 매장에 대한 접근 권한이 없습니다.");
    }
}

구현 예정

  • 인기 매장 추천 API
profile
Turning Vision into Reality.

0개의 댓글