package io.company.localhost.service; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import io.company.localhost.common.dto.MapDto; import io.company.localhost.mapper.localvacaMapper; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class localvacaService { private final localvacaMapper localvacaMapper; private final RestTemplate restTemplate = new RestTemplate(); @Value("${api.public-holiday.key}") private String serviceKey; public void insertVacation(MapDto vacation) { // 필요한 경우 데이터 검증/전처리 후 매퍼 호출 localvacaMapper.insertVacation(vacation); } public void deleteVacation(Long vacationId) { localvacaMapper.deleteVacation(vacationId); } public List selectVacationList(int year, int month) { return localvacaMapper.selectVacations(year, month); } /** * 🔹 특정 연월에 대한 공휴일 데이터 조회 */ public List selectHolidays(int year, int month) { // ✅ ServiceKey를 디코딩해서 사용 String decodedServiceKey = URLDecoder.decode(serviceKey, StandardCharsets.UTF_8); System.out.println("📌 디코딩된 ServiceKey: " + decodedServiceKey); // ✅ URI를 직접 문자열로 구성하여 ServiceKey가 다시 인코딩되지 않도록 함 String url = "http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo" + "?solYear=" + year + "&solMonth=" + String.format("%02d", month) + "&ServiceKey=" + decodedServiceKey // ✅ 디코딩된 상태로 직접 추가 + "&_type=json"; System.out.println("📌 API 요청 URL: " + url); // ✅ API 요청 헤더 추가 (User-Agent 포함) HttpHeaders headers = new HttpHeaders(); headers.set(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); HttpEntity entity = new HttpEntity<>(headers); // ✅ `exchange` 메서드를 사용하여 요청 ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class); System.out.println("📌 API 응답 데이터: " + response.getBody()); return parseResponse(response.getBody()); } /** * 🔹 공휴일 데이터를 MapDto로 변환 */ private List parseResponse(Map response) { List holidays = new ArrayList<>(); if (response == null || !response.containsKey("response")) { System.out.println("📌 응답이 비어 있음."); return holidays; } Map responseBody = (Map) response.get("response"); System.out.println("📌 responseBody: " + responseBody); if (responseBody == null || !responseBody.containsKey("body")) { System.out.println("📌 API 응답 데이터에서 'body' 필드 없음."); return holidays; } Map body = (Map) responseBody.get("body"); System.out.println("📌 body: " + body); if (body == null || !body.containsKey("items")) { System.out.println("📌 API 응답 데이터에서 'items' 필드 없음."); return holidays; } Object items = body.get("items"); System.out.println("📌 items: " + items); // ✅ 'items'가 Map 타입인지 확인 후 처리 if (items instanceof Map) { Map itemMap = (Map) items; if (itemMap.containsKey("item")) { Object itemData = itemMap.get("item"); System.out.println("📌 itemData: " + itemData); // ✅ 'item'이 리스트인지 단일 객체인지 확인 후 변환 if (itemData instanceof List) { for (Map item : (List>) itemData) { holidays.add(convertToMapDto(item)); } } else if (itemData instanceof Map) { holidays.add(convertToMapDto((Map) itemData)); } } } System.out.println("📌 최종 holidays: " + holidays); return holidays; } /** * 🔹 공휴일 데이터를 MapDto로 변환 */ private MapDto convertToMapDto(Map item) { MapDto dto = new MapDto(); // ✅ locdate를 안전하게 변환 (int, long, double 등 다양한 형태 대응) String locdateStr = String.valueOf(item.get("locdate")); if (locdateStr.contains(".")) { locdateStr = locdateStr.split("\\.")[0]; // 소수점 제거 } // ✅ YYYY-MM-DD 형식으로 변환 if (locdateStr.length() == 8) { String formattedDate = locdateStr.substring(0, 4) + "-" + locdateStr.substring(4, 6) + "-" + locdateStr.substring(6, 8); dto.put("date", formattedDate); } else { dto.put("date", locdateStr); // 변환 불가능한 경우 원본 저장 } dto.put("name", item.get("dateName")); return dto; } /** * 내 연차 사용 내역 조회 (사용한 연차 & 받은 연차) */ public Map> selectUserVacationHistory(Long userId, int year) { List usedVacations = localvacaMapper.selectUsedVacations(userId,year); List receivedVacations = localvacaMapper.selectReceivedVacations(userId,year); Map> history = new HashMap<>(); history.put("usedVacations", usedVacations); history.put("receivedVacations", receivedVacations); return history; } /** * 사원별 남은 연차 개수 조회 */ public List selectEmployeeRemainingVacation() { List employeeVacations = localvacaMapper.selectEmployeeRemainingVacation(); return employeeVacations.stream().map(emp -> { // 🔹 hireDate 변환 (포맷 정규화) String hireDateString = emp.get("hireDate").toString().split("\\.")[0]; // .0 제거 LocalDate hireDate; try { if (hireDateString.contains("T")) { hireDate = LocalDateTime.parse(hireDateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); } else { hireDate = LocalDate.parse(hireDateString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } } catch (Exception e) { throw new RuntimeException("🚨 입사일 변환 오류: " + hireDateString, e); } // 🔹 총 연차 개수 계산 int totalVacation = procCalculateTotalVacation(hireDate); // 🔹 사용한 연차 개수 처리 (null 방지) double usedVacation = emp.get("used_quota") != null ? ((Number) emp.get("used_quota")).doubleValue() : 0.0; // 🔹 받은 연차 개수 처리 (null 방지) double receivedVacation = emp.get("received_quota") != null ? ((Number) emp.get("received_quota")).doubleValue() : 0.0; // 🔹 남은 연차 개수 계산 (반차 포함) double remainingVacation = totalVacation - usedVacation + receivedVacation; // 🔹 값 업데이트 emp.put("totalQuota", totalVacation); emp.put("remainingQuota", remainingVacation); emp.put("usedQuota", usedVacation); emp.put("receivedQuota", receivedVacation); return emp; }).collect(Collectors.toList()); } /** * 총 연차 계산 로직 */ private int procCalculateTotalVacation(LocalDate hireDate) { LocalDate today = LocalDate.now(); int yearsWorked = hireDate.until(today).getYears(); // 🔹 1년 미만: 연간 12개 지급 if (yearsWorked < 1) { return 12; } // 🔹 1년 이상 기본 15개 int totalVacation = 15; LocalDate nextIncreaseDate = hireDate.plusYears(2).withMonth(1).withDayOfMonth(1); // 🔹 2년마다 1개 추가 while (nextIncreaseDate.isBefore(today) || nextIncreaseDate.isEqual(today)) { totalVacation += 1; nextIncreaseDate = nextIncreaseDate.plusYears(2); } return totalVacation; } public List selectSentVacationCount(MapDto map) { return localvacaMapper.selectSentVacationCount(map); } }