구현한 기능
- 매장별 예약 통계 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()
));
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, "해당 매장에 대한 접근 권한이 없습니다.");
}
}
구현 예정