Dashboard 용 웹서비스 추가.

This commit is contained in:
Hyojin Ahn 2026-06-18 09:30:57 -04:00
parent 506440bc2e
commit fba94438fa
7 changed files with 336 additions and 0 deletions

View File

@ -0,0 +1,71 @@
package com.goi.erp.controller;
import com.goi.erp.common.permission.PermissionChecker;
import com.goi.erp.common.permission.PermissionSet;
import com.goi.erp.dto.DriverDailyStatHistoryResponseDto;
import com.goi.erp.dto.DriverDailyStatResponseDto;
import com.goi.erp.service.DriverDailyStatService;
import com.goi.erp.token.PermissionAuthenticationToken;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/driver-daily-stat")
@RequiredArgsConstructor
public class DriverDailyStatController {
private final DriverDailyStatService driverDailyStatService;
private PermissionSet getPermission() {
PermissionAuthenticationToken auth =
(PermissionAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
if (auth == null || auth.getPermissionSet() == null) {
throw new AccessDeniedException("Permission information is missing");
}
return auth.getPermissionSet();
}
// READ (대시보드: date default=오늘, driverId default=전체)
@GetMapping
public ResponseEntity<List<DriverDailyStatResponseDto>> getDashboard(
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
@RequestParam(required = false) Long driverId) {
PermissionSet permissions = getPermission();
if (!PermissionChecker.canReadCRMAll(permissions)) {
throw new AccessDeniedException("You do not have permission to read dispatch daily status");
}
return ResponseEntity.ok(driverDailyStatService.getDashboard(date, driverId));
}
// READ HISTORY (단일 드라이버 누적 이력: driverId 필수, date default=오늘)
@GetMapping("/history")
public ResponseEntity<DriverDailyStatHistoryResponseDto> getDriverHistory(
@RequestParam Long driverId,
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
PermissionSet permissions = getPermission();
if (!PermissionChecker.canReadCRMAll(permissions)) {
throw new AccessDeniedException("You do not have permission to read dispatch history");
}
return ResponseEntity.ok(driverDailyStatService.getDriverHistory(date, driverId));
}
}

View File

@ -0,0 +1,22 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DriverDailyStatHistoryPointDto {
private LocalDateTime t; // 픽업 시각
private String customerName; // 고객명 (cus_name)
private BigDecimal qty; // 픽업의
private BigDecimal cumQty; // 누적 픽업량
private Long cumCompleted; // 누적 방문 완료
}

View File

@ -0,0 +1,20 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DriverDailyStatHistoryResponseDto {
private Long driverId; // 드라이버 ID
private LocalDate date; // 조회 일자
private List<DriverDailyStatHistoryPointDto> points; // 시각순 누적 포인트 (라인 1개)
}

View File

@ -0,0 +1,37 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DriverDailyStatResponseDto {
private Long ddsId; // 식별자 (프론트 row key용, vd.ved_id)
private String ddsStatus; // 배차 상태 (O )
private Integer ddsShift; // 교대(shift)
private LocalDate ddsDate; // 배차 일자
private String ddsNumber; // 차량 번호 (: 9호)
private Long ddsDriverId; // 드라이버 ID (필터/드롭다운용)
private String ddsMainDriver; // 드라이버
private String ddsSubDriver; // 보조 드라이버
private String ddsKm; // 이동거리 (: "35.78 km")
private String ddsMin; // 운행시간 (: "198.00 min")
private Integer ddsEvent; // 안전 이벤트
private BigDecimal ddsTotalQty; // 실제 픽업량 합계
private BigDecimal ddsPlannedQty; // 예상 픽업량 합계 (목표)
private Long ddsCompletedCount; // 방문 완료(F)
private Long ddsPlannedCount; // 전체 방문 목표
private LocalDateTime ddsStartAt; // 운행 시작
private LocalDateTime ddsPausedAt; // 일시정지
private LocalDateTime ddsEndAt; // 운행 종료
private String ddsEndReason; // 종료 사유
}

View File

@ -0,0 +1,48 @@
package com.goi.erp.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.hibernate.annotations.Immutable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Immutable // 읽기 전용
@Table(schema = "rpt", name = "vw_driver_daily_stat") // 실제 생성한 스키마/ 이름에 맞춰 조정
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DriverDailyStat {
@Id
private Long ddsId; // vd.ved_id ( PK용, SELECT에 dds_id 추가 필요)
private String ddsStatus; // 배차 상태 (O )
private Integer ddsShift; // 교대(shift)
private LocalDate ddsDate; // 배차 일자
private String ddsNumber; // 차량 번호 (: 9호)
private Long ddsDriverId; // 드라이버 ID (필터/드롭다운용)
private String ddsMainDriver; // 드라이버
private String ddsSubDriver; // 보조 드라이버
private String ddsKm; // 이동거리 (뷰에서 텍스트, : "35.78 km")
private String ddsMin; // 운행시간 (뷰에서 텍스트, : "198.00 min")
private Integer ddsEvent; // 안전 이벤트
private BigDecimal ddsTotalQty; // 실제 픽업량 합계
private BigDecimal ddsPlannedQty; // 예상 픽업량 합계 (목표)
private Long ddsCompletedCount; // 방문 완료(F)
private Long ddsPlannedCount; // 전체 방문 목표
private LocalDateTime ddsStartAt; // 운행 시작
private LocalDateTime ddsPausedAt; // 일시정지
private LocalDateTime ddsEndAt; // 운행 종료
private String ddsEndReason; // 종료 사유
}

View File

@ -0,0 +1,41 @@
package com.goi.erp.repository;
import com.goi.erp.entity.DriverDailyStat;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface DriverDailyStatRepository extends JpaRepository<DriverDailyStat, Long> {
// 날짜 기준 전체 드라이버 (운행 시작 시간 )
List<DriverDailyStat> findByDdsDateOrderByDdsStartAtAsc(LocalDate ddsDate);
// 날짜 + 특정 드라이버 (운행 시작 시간 )
List<DriverDailyStat> findByDdsDateAndDdsDriverIdOrderByDdsStartAtAsc(LocalDate ddsDate, Long ddsDriverId);
// [단일 드라이버] 픽업 이벤트 기반 누적 이력 (시각순) 라인 차트용
// - crm.customer_daily_order cdo_pickup_at 으로 누적합
// - return: [0]=픽업 시각, [1]=누적 픽업량, [2]=누적 방문완료
@Query(value = """
SELECT cdo.cdo_pickup_at AS t,
c.cus_name AS cus_name,
cdo.cdo_quantity AS qty,
SUM(cdo.cdo_quantity) OVER (ORDER BY cdo.cdo_pickup_at) AS cum_qty,
COUNT(*) FILTER (WHERE cast(cdo.cdo_status as text) = 'F')
OVER (ORDER BY cdo.cdo_pickup_at) AS cum_completed
FROM crm.customer_daily_order cdo
LEFT JOIN crm.customer c ON c.cus_id = cdo.cdo_customer_id
WHERE cdo.cdo_order_date = :date
AND cdo.cdo_driver_id = :driverId
AND cdo.cdo_pickup_at IS NOT NULL
ORDER BY cdo.cdo_pickup_at
""", nativeQuery = true)
List<Object[]> findDriverPickupHistory(@Param("date") LocalDate date,
@Param("driverId") Long driverId);
}

View File

@ -0,0 +1,97 @@
package com.goi.erp.service;
import com.goi.erp.dto.DriverDailyStatHistoryPointDto;
import com.goi.erp.dto.DriverDailyStatHistoryResponseDto;
import com.goi.erp.dto.DriverDailyStatResponseDto;
import com.goi.erp.entity.DriverDailyStat;
import com.goi.erp.repository.DriverDailyStatRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DriverDailyStatService {
private final DriverDailyStatRepository driverDailyStatRepository;
/**
* 대시보드 조회
* @param date 조회 일자 (null 이면 오늘)
* @param driverId 드라이버 ID (null 이면 전체)
*/
public List<DriverDailyStatResponseDto> getDashboard(LocalDate date, Long driverId) {
LocalDate targetDate = (date == null) ? LocalDate.now() : date;
List<DriverDailyStat> rows = (driverId == null)
? driverDailyStatRepository.findByDdsDateOrderByDdsStartAtAsc(targetDate)
: driverDailyStatRepository.findByDdsDateAndDdsDriverIdOrderByDdsStartAtAsc(targetDate, driverId);
return rows.stream().map(this::mapToDto).toList();
}
/**
* 단일 드라이버 누적 이력 (이벤트 시각 기반, 라인 차트용)
* @param date 조회 일자 (null 이면 오늘)
* @param driverId 드라이버 ID (필수)
*/
public DriverDailyStatHistoryResponseDto getDriverHistory(LocalDate date, Long driverId) {
LocalDate targetDate = (date == null) ? LocalDate.now() : date;
List<DriverDailyStatHistoryPointDto> points = driverDailyStatRepository
.findDriverPickupHistory(targetDate, driverId)
.stream()
.map(r -> DriverDailyStatHistoryPointDto.builder()
.t(((Timestamp) r[0]).toLocalDateTime())
.customerName((String) r[1])
.qty(r[2] == null ? BigDecimal.ZERO : (BigDecimal) r[2])
.cumQty(r[3] == null ? BigDecimal.ZERO : (BigDecimal) r[3])
.cumCompleted(r[4] == null ? 0L : ((Number) r[4]).longValue())
.build())
.toList();
return DriverDailyStatHistoryResponseDto.builder()
.driverId(driverId)
.date(targetDate)
.points(points)
.build();
}
/**
* ENTITY RESPONSE DTO
*/
public DriverDailyStatResponseDto mapToDto(DriverDailyStat s) {
if (s == null) return null;
return DriverDailyStatResponseDto.builder()
.ddsId(s.getDdsId())
.ddsDriverId(s.getDdsDriverId())
.ddsStatus(s.getDdsStatus())
.ddsShift(s.getDdsShift())
.ddsDate(s.getDdsDate())
.ddsNumber(s.getDdsNumber())
.ddsMainDriver(s.getDdsMainDriver())
.ddsSubDriver(s.getDdsSubDriver())
.ddsKm(s.getDdsKm())
.ddsMin(s.getDdsMin())
.ddsEvent(s.getDdsEvent())
.ddsTotalQty(s.getDdsTotalQty())
.ddsPlannedQty(s.getDdsPlannedQty())
.ddsCompletedCount(s.getDdsCompletedCount())
.ddsPlannedCount(s.getDdsPlannedCount())
.ddsStartAt(s.getDdsStartAt())
.ddsPausedAt(s.getDdsPausedAt())
.ddsEndAt(s.getDdsEndAt())
.ddsEndReason(s.getDdsEndReason())
.build();
}
}