고객 수 추이 추가.
This commit is contained in:
parent
8736e0eb2e
commit
0b2e744a0f
|
|
@ -5,6 +5,7 @@ import com.goi.erp.common.permission.PermissionSet;
|
||||||
import com.goi.erp.dto.CustomerDailyOrderResponseDto;
|
import com.goi.erp.dto.CustomerDailyOrderResponseDto;
|
||||||
import com.goi.erp.dto.CustomerRequestDto;
|
import com.goi.erp.dto.CustomerRequestDto;
|
||||||
import com.goi.erp.dto.CustomerResponseDto;
|
import com.goi.erp.dto.CustomerResponseDto;
|
||||||
|
import com.goi.erp.dto.CustomerActiveTrendPointDto;
|
||||||
import com.goi.erp.dto.CustomerSummaryResponseDto;
|
import com.goi.erp.dto.CustomerSummaryResponseDto;
|
||||||
import com.goi.erp.service.CustomerDailyOrderService;
|
import com.goi.erp.service.CustomerDailyOrderService;
|
||||||
import com.goi.erp.service.CustomerService;
|
import com.goi.erp.service.CustomerService;
|
||||||
|
|
@ -22,6 +23,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
|
@ -104,6 +106,25 @@ public class CustomerController {
|
||||||
return ResponseEntity.ok(customerService.getCustomerSummary());
|
return ResponseEntity.ok(customerService.getCustomerSummary());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACTIVE TREND (CS 홈 선그래프: 최근 N일 날짜별 활성 고객 수, 기본 7일)
|
||||||
|
@GetMapping("/active-trend")
|
||||||
|
public ResponseEntity<List<CustomerActiveTrendPointDto>> getActiveCustomerTrend(
|
||||||
|
@RequestParam(required = false, defaultValue = "7") int days) {
|
||||||
|
// 권한 체크
|
||||||
|
PermissionAuthenticationToken auth = (PermissionAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
if (auth == null || auth.getPermissionSet() == null) {
|
||||||
|
throw new AccessDeniedException("Permission information is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionSet permissionSet = auth.getPermissionSet();
|
||||||
|
if (!PermissionChecker.canReadCRMAll(permissionSet)) {
|
||||||
|
throw new AccessDeniedException("You do not have permission to read all CRM data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(customerService.getActiveCustomerTrend(days));
|
||||||
|
}
|
||||||
|
|
||||||
// READ ONE
|
// READ ONE
|
||||||
@GetMapping("/uuid/{uuid}")
|
@GetMapping("/uuid/{uuid}")
|
||||||
public ResponseEntity<CustomerResponseDto> getCustomer(@PathVariable UUID uuid) {
|
public ResponseEntity<CustomerResponseDto> getCustomer(@PathVariable UUID uuid) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.goi.erp.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CustomerActiveTrendPointDto {
|
||||||
|
|
||||||
|
// 해당 날짜 (YYYY-MM-DD)
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
|
||||||
|
private LocalDate date;
|
||||||
|
|
||||||
|
// 그 날짜에 활성( cus_contract_date <= date AND (terminated IS NULL OR terminated > date) )인 고객 수
|
||||||
|
private long count;
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,11 @@ import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -22,13 +24,32 @@ public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
||||||
|
|
||||||
boolean existsByCusNo(String cusNo);
|
boolean existsByCusNo(String cusNo);
|
||||||
|
|
||||||
@Query(value = "select c from Customer c order by c.cusContractDate desc nulls last",
|
|
||||||
countQuery = "select count(c) from Customer c")
|
|
||||||
Page<Customer> findAllOrderByContractDateDesc(Pageable pageable);
|
|
||||||
|
|
||||||
// 현재(활성) 고객 수: cus_status = 'A'
|
// 현재(활성) 고객 수: cus_status = 'A'
|
||||||
long countByCusStatus(String cusStatus);
|
long countByCusStatus(String cusStatus);
|
||||||
|
|
||||||
// 오늘 추가된 고객 수: cus_contract_date = 오늘
|
// 오늘 추가된 고객 수: cus_contract_date = 오늘
|
||||||
long countByCusContractDate(LocalDate cusContractDate);
|
long countByCusContractDate(LocalDate cusContractDate);
|
||||||
|
|
||||||
|
// 날짜별 활성 고객 수 (선그래프용).
|
||||||
|
// - 활성 기준 = cus_status='A' (현재 활성 플래그가 진짜 기준)
|
||||||
|
// - 그 위에 날짜로 과거 시점 재구성: cus_contract_date <= D AND (cus_terminated_date IS NULL OR cus_terminated_date > D)
|
||||||
|
// - 즉 "지금 활성('A')이면서 그 날짜에 계약기간 안에 있던" 고객 수 → 오늘 값은 전체(활성) 고객 수와 일치
|
||||||
|
// - generate_series 로 [startDate, endDate] 각 날짜를 만들고 LEFT JOIN 으로 카운트 → 빈 날도 0으로 채워짐
|
||||||
|
// - return: [0]=날짜(date), [1]=활성 고객 수(bigint)
|
||||||
|
@Query(value = """
|
||||||
|
SELECT CAST(series.day AS date) AS day,
|
||||||
|
COUNT(c.cus_id) AS active_count
|
||||||
|
FROM generate_series(CAST(:startDate AS timestamp),
|
||||||
|
CAST(:endDate AS timestamp),
|
||||||
|
INTERVAL '1 day') AS series(day)
|
||||||
|
LEFT JOIN crm.customer c
|
||||||
|
ON c.cus_status = 'A'
|
||||||
|
AND c.cus_contract_date IS NOT NULL
|
||||||
|
AND c.cus_contract_date <= CAST(series.day AS date)
|
||||||
|
AND (c.cus_terminated_date IS NULL OR c.cus_terminated_date > CAST(series.day AS date))
|
||||||
|
GROUP BY series.day
|
||||||
|
ORDER BY series.day
|
||||||
|
""", nativeQuery = true)
|
||||||
|
List<Object[]> findActiveCustomerTrend(@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate);
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.goi.erp.service;
|
||||||
|
|
||||||
import com.goi.erp.dto.CustomerRequestDto;
|
import com.goi.erp.dto.CustomerRequestDto;
|
||||||
import com.goi.erp.dto.CustomerResponseDto;
|
import com.goi.erp.dto.CustomerResponseDto;
|
||||||
|
import com.goi.erp.dto.CustomerActiveTrendPointDto;
|
||||||
import com.goi.erp.dto.CustomerSummaryResponseDto;
|
import com.goi.erp.dto.CustomerSummaryResponseDto;
|
||||||
import com.goi.erp.entity.Customer;
|
import com.goi.erp.entity.Customer;
|
||||||
import com.goi.erp.entity.EntityChangeLog;
|
import com.goi.erp.entity.EntityChangeLog;
|
||||||
|
|
@ -21,6 +22,8 @@ import java.lang.reflect.Field;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -84,7 +87,7 @@ public class CustomerService {
|
||||||
|
|
||||||
public Page<CustomerResponseDto> getAllCustomers(int page, int size) {
|
public Page<CustomerResponseDto> getAllCustomers(int page, int size) {
|
||||||
Pageable pageable = PageRequest.of(page, size);
|
Pageable pageable = PageRequest.of(page, size);
|
||||||
Page<Customer> customers = customerRepository.findAllOrderByContractDateDesc(pageable);
|
Page<Customer> customers = customerRepository.findAll(pageable);
|
||||||
return customers.map(this::mapToDto); // 기존 mapToDto 사용
|
return customers.map(this::mapToDto); // 기존 mapToDto 사용
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +105,34 @@ public class CustomerService {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CS 홈 선그래프: 최근 N일(기본 7) 날짜별 활성 고객 수
|
||||||
|
public List<CustomerActiveTrendPointDto> getActiveCustomerTrend(int days) {
|
||||||
|
int d = Math.min(Math.max(days, 1), 31); // 1~31일로 제한
|
||||||
|
LocalDate end = LocalDate.now();
|
||||||
|
LocalDate start = end.minusDays(d - 1L); // end 포함 N개 지점
|
||||||
|
|
||||||
|
List<Object[]> rows = customerRepository.findActiveCustomerTrend(start, end);
|
||||||
|
|
||||||
|
List<CustomerActiveTrendPointDto> result = new ArrayList<>(rows.size());
|
||||||
|
for (Object[] row : rows) {
|
||||||
|
LocalDate day;
|
||||||
|
if (row[0] instanceof java.sql.Date sqlDate) {
|
||||||
|
day = sqlDate.toLocalDate();
|
||||||
|
} else if (row[0] instanceof LocalDate ld) {
|
||||||
|
day = ld;
|
||||||
|
} else {
|
||||||
|
day = LocalDate.parse(row[0].toString());
|
||||||
|
}
|
||||||
|
long count = ((Number) row[1]).longValue();
|
||||||
|
|
||||||
|
result.add(CustomerActiveTrendPointDto.builder()
|
||||||
|
.date(day)
|
||||||
|
.count(count)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public CustomerResponseDto getCustomerByUuid(UUID uuid) {
|
public CustomerResponseDto getCustomerByUuid(UUID uuid) {
|
||||||
Customer customer = customerRepository.findByCusUuid(uuid)
|
Customer customer = customerRepository.findByCusUuid(uuid)
|
||||||
.orElseThrow(() -> new RuntimeException("Customer not found"));
|
.orElseThrow(() -> new RuntimeException("Customer not found"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue