diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonRemarkBuilder.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonRemarkBuilder.java index 953063c..f8b6e10 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonRemarkBuilder.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonRemarkBuilder.java @@ -94,7 +94,7 @@ public class ComparisonRemarkBuilder { // 날짜 포맷 변환 (YYYYMMDD -> YY.M.D) String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd()); - String step1OwnerName = StringUtil.nvl(step1Record.getRprsOwnrNm()); + String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm()); StringBuilder sb = new StringBuilder(); @@ -124,6 +124,67 @@ public class ComparisonRemarkBuilder { } + /** + * 명의이전 비고 생성 (내사종결 또는 날짜 수정 후 부과) + * + * 비고 형식: + * 명의이전(25.9.3.) 이전소유자 상품용 + * 22루2283 + * 검사기간 시작일자 - 종료일자 + * 검사일 일자 + * 명의이전 일자 + * 상품용 일자 + * + * @param step1Record Step 1 API 응답 (검사일 기준 소유자명) + * @param step4Record Step 4 API 응답 (명의이전 시점 소유자명 = 상품용) + * @param ledgerRecord 조건에 맞는 갑부 레코드 (명의이전 레코드) + * @param vhclno 차량번호 + * @param inspYmd 검사일자 + * @param vldPrdExpryYmd 유효기간만료일 + * @param inspEndYmd 검사종료일자 + * @param daysBetween 일수차이 + * @return 비고 문자열 + */ + public static String buildOwnerChangeRemark(NewBasicResponse.Record step1Record, + NewBasicResponse.Record step4Record, + NewLedgerResponse.Record ledgerRecord, + String vhclno, + String inspYmd, + String vldPrdExpryYmd, + String inspEndYmd, + long daysBetween) { + + // 날짜 포맷 변환 (YYYYMMDD -> YY.M.D) + String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd()); + String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm()); + + StringBuilder sb = new StringBuilder(); + + // 첫 줄: 명의이전(25.9.3.) 이전소유자 상품용 + sb.append("명의이전(").append(chgYmdFormatted).append(")").append("\n"); + + // 둘째 줄: 차량번호 + sb.append(StringUtil.nvl(vhclno)).append("\n"); + + // 셋째 줄: 검사기간 시작일자 - 종료일자 + sb.append(" - 검사기간: ").append(DateUtil.formatDateString(vldPrdExpryYmd)) + .append(" - ").append(DateUtil.formatDateString(inspEndYmd)).append("\n"); + + // 넷째 줄: 검사일 일자 + sb.append(" - 검사일: ").append(DateUtil.formatDateString(inspYmd)).append("\n"); + + // 다섯째 줄: 명의이전 일자 + sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n"); + + // 여섯째 줄: 상품용 일자 (명의이전 일자와 동일) + sb.append(" - 상품용: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())); + + // 일곱째 줄: 일수차이 + sb.append("일수차이: ").append(daysBetween).append("일"); + + return sb.toString(); + } + /** * 등록원부 갑부 레코드 상세 정보 생성 * diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java index 80222f9..0bb632d 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java @@ -43,6 +43,12 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + /** + * 명의이전 또는 상품용 관련 일수 기준값 (일) + * 명의이전일자 ~ 검사일 사이의 일수가 이 값 이하면 내사종결, 초과하면 날짜 수정 후 부과 + */ + private static final int DAYS_THRESHOLD = 31; + /** * 비교 로직 메인 메서드 * @@ -147,17 +153,17 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co NewBasicResponse.Record step1Record = step1Response.getRecord().get(0); String vin = step1Record.getVin(); // 차대번호 - String step1OwnerName = step1Record.getRprsOwnrNm(); // 검사일 기준 소유자명 + String step1wnerName = step1Record.getRprsOwnrNm(); // 검사일 기준 소유자명 - log.info("[상품용] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName); + log.info("[상품용] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1wnerName); // 조건 1: 소유자명에 "상품용" 포함 여부 확인 - if (step1OwnerName == null || !step1OwnerName.contains("상품용")) { - log.debug("[상품용] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + if (step1wnerName == null || !step1wnerName.contains("상품용")) { + log.debug("[상품용] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1wnerName); return null; } - log.info("[상품용] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + log.info("[상품용] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1wnerName); // ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ========== String today = LocalDate.now().format(DATE_FORMATTER); @@ -270,12 +276,12 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co log.info("[상품용] Step 4 결과 - 소유자명: {}", step4OwnerName); // ========== 소유자명 비교 ========== - if (step4OwnerName == null || !step4OwnerName.equals(step1OwnerName)) { - log.debug("[상품용] 소유자명 불일치 - Step1 소유자명: {}, Step4 소유자명: {}", step1OwnerName, step4OwnerName); + if (step4OwnerName == null || !step4OwnerName.equals(step1wnerName)) { + log.debug("[상품용] 소유자명 불일치 - Step1 소유자명: {}, Step4 소유자명: {}", step1wnerName, step4OwnerName); return null; } - log.info("[상품용] 소유자명 일치 확인! - 소유자명: {}", step1OwnerName); + log.info("[상품용] 소유자명 일치 확인! - 소유자명: {}", step1wnerName); // ========== 필요 없음. // 어차피 상단에서 TB_CAR_FFNLG_TRGT.검사종료일자 작은것중의 마지막일자를 찾기때문에 @@ -458,15 +464,15 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co return null; } - // 조건: 가장 마지막 명의이전일자가 검사일의 31일 이내인지 확인 + // 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 이내인지 확인 long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate); - if (daysBetween < 0 || daysBetween > 31) { - log.debug("[내사종결] 명의이전일자가 검사일의 31일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일", - targetRecord.getChgYmd(), inspYmd, daysBetween); + if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) { + log.debug("[내사종결] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween); return null; } - log.info("[내사종결] 명의이전일자가 검사일의 31일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", - targetRecord.getChgYmd(), inspYmd, daysBetween); + log.info("[내사종결] 명의이전일자가 검사일의 {}일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween); String targetChgYmd = targetRecord.getChgYmd(); log.info("[내사종결] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm()); @@ -491,13 +497,11 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co log.info("[내사종결] Step 4 결과 - 소유자명: {}", step4OwnerName); // ========== 소유자명 비교 - 상품용 포함 여부 확인 ========== - if (step4OwnerName == null || !step4OwnerName.contains("상품용")) { - log.debug("[내사종결] 소유자명에 '상품용' 미포함 - Step4 소유자명: {}", step4OwnerName); + if (step4OwnerName == null || step4OwnerName.contains("상품용")) { + log.debug("[내사종결] 소유자명에 '상품용' 포함 - Step4 소유자명: {}", step4OwnerName); return null; } - log.info("[내사종결] 명의이전 시점 소유자명에 '상품용' 확인! - 소유자명: {}", step4OwnerName); - log.info("[내사종결] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd); // ========== 비고 생성 ========== @@ -533,7 +537,211 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co } /** - * 3. 날짜 수정 후 부과 검증 - 명의이전 이전소유자 상품용, 31일 초과 + * 3. 내사종결 검증 - 명의이전, 31일 이내 + * + * API 호출 3단계: + * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 + * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 + * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List + * + * 비교조건: + * - 1단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기 + * • 유효기간만료일 <= CHG_YMD <= 검사종료일자 + * • CHG_TASK_SE_CD == "11" (명의이전) + * - 2단계: 명의이전일자와 검사일 비교 + * • 명의이전일자 ~ 검사일까지의 일수 계산 + * • 31일 이내인 경우 + * + * @param existingData 과태료 대상 데이터 + * @return 04 (내사종결) 또는 null (미적용) + */ + private String checkInvestigationClosedByOwnerChangeWithin31Days(CarFfnlgTrgtVO existingData) { + String vhclno = existingData.getVhclno(); + String inspYmd = existingData.getInspYmd(); + String vldPrdExpryYmd = existingData.getVldPrdExpryYmd(); + String inspEndYmd = existingData.getInspEndYmd(); + + try { + // ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ========== + log.info("[내사종결-명의이전] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd); + + NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd); + NewBasicResponse step1Response = apiService.getBasicInfo(step1Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId()); + + if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) { + log.warn("[내사종결-명의이전] Step 1 응답 없음 - 차량번호: {}", vhclno); + return null; + } + + NewBasicResponse.Record step1Record = step1Response.getRecord().get(0); + String vin = step1Record.getVin(); // 차대번호 + String step1OwnerName = step1Record.getRprsOwnrNm(); // 검사일 기준 소유자명 + + log.info("[내사종결-명의이전] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName); + + // 조건: 소유자명에 "상품용" 미포함 여부 확인 (명의이전 케이스는 상품용이 아니어야 함) + if (step1OwnerName != null && step1OwnerName.contains("상품용")) { + log.debug("[내사종결-명의이전] 소유자명에 '상품용' 포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + return null; + } + + log.info("[내사종결-명의이전] 소유자명에 '상품용' 미포함 확인 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + + // ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ========== + String today = LocalDate.now().format(DATE_FORMATTER); + log.info("[내사종결-명의이전] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today); + + NewBasicRequest step2Request = createBasicRequest(null, vin, today); + NewBasicResponse step2Response = apiService.getBasicInfo(step2Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtId()); + + if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) { + log.warn("[내사종결-명의이전] Step 2 응답 없음 - 차대번호: {}", vin); + return null; + } + + NewBasicResponse.Record step2Record = step2Response.getRecord().get(0); + String currentVhclno = step2Record.getVhrno(); + String currentOwnerName = step2Record.getRprsOwnrNm(); + String currentIdecno = step2Record.getRprsvOwnrIdecno(); + String currentLegalDongCode = step2Record.getUsgsrhldStdgCd(); + + log.info("[내사종결-명의이전] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", + currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + + // ========== Step 3: 자동차등록원부(갑) 조회 ========== + log.info("[내사종결-명의이전] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", + currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + + NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request); + ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtId()); + + if (step3Response == null) { + log.warn("[내사종결-명의이전] Step 3 응답 없음 - 차량번호: {}", currentVhclno); + return null; + } + + List ledgerRecords = step3Response.getRecord(); + if (ledgerRecords == null || ledgerRecords.isEmpty()) { + log.debug("[내사종결-명의이전] 갑부 상세 내역 없음 - 차량번호: {}", vhclno); + return null; + } + + // ========== 갑부 상세에서 검사기간 내 명의이전 레코드 찾기 ========== + log.info("[내사종결-명의이전] 갑부 상세 레코드 검색 시작 - 유효기간만료일: {}, 검사종료일자: {}", vldPrdExpryYmd, inspEndYmd); + + NewLedgerResponse.Record targetRecord = null; + LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd); + LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd); + + for (NewLedgerResponse.Record record : ledgerRecords) { + String chgYmd = record.getChgYmd(); + String chgTaskSeCd = record.getChgTaskSeCd(); + + // 조건: CHG_TASK_SE_CD == "11" (명의이전) + if (!"11".equals(chgTaskSeCd) || chgYmd == null) { + continue; + } + + LocalDate chgDate = DateUtil.parseDate(chgYmd); + if (chgDate == null) { + continue; + } + + // 조건: 유효기간만료일 <= CHG_YMD <= 검사종료일자 + if ((chgDate.isEqual(vldPrdExpryDate) || chgDate.isAfter(vldPrdExpryDate)) && + (chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) { + targetRecord = record; + log.debug("[내사종결-명의이전] 검사기간 내 명의이전 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd); + break; // 첫 번째 발견된 명의이전 사용 + } + } + + if (targetRecord == null) { + log.debug("[내사종결-명의이전] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno); + return null; + } + + String targetChgYmd = targetRecord.getChgYmd(); + log.info("[내사종결-명의이전] 검사기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm()); + + // ========== 명의이전일자 ~ 검사일 사이의 일수 계산 ========== + LocalDate chgDate = DateUtil.parseDate(targetChgYmd); + LocalDate inspDate = DateUtil.parseDate(inspYmd); + long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, inspDate); + + if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) { + log.debug("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween); + return null; + } + + log.info("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween); + + // ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD -1일) ========== + LocalDate targetDate = DateUtil.parseDate(targetChgYmd); + String targetChgYmdMinus1 = targetDate.minusDays(1).format(DATE_FORMATTER); + log.info("[내사종결-명의이전] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {} (원본: {})", vin, targetChgYmdMinus1, targetChgYmd); + + NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmdMinus1); + NewBasicResponse step4Response = apiService.getBasicInfo(step4Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtId()); + + if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) { + log.warn("[내사종결-명의이전] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmdMinus1); + return null; + } + + NewBasicResponse.Record step4Record = step4Response.getRecord().get(0); + String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명 + + log.info("[내사종결-명의이전] Step 4 결과 - 소유자명: {}", step4OwnerName); + + // ========== 소유자명 비교 - 명의이전 전 소유자가 상품용이어야 함 ========== + if (step4OwnerName == null || !step4OwnerName.contains("상품용")) { + log.debug("[내사종결-명의이전] 명의이전 전 소유자명에 '상품용' 미포함 - Step4 소유자명: {}", step4OwnerName); + return null; + } + + log.info("[내사종결-명의이전] 명의이전 - 소유자명: {}", step4OwnerName); + + // ========== 비고 생성 ========== + String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark( + step1Record, step4Record, targetRecord, + vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween + ); + + // ========== DB 업데이트 ========== + existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); + existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); + existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED); + existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER)); + existingData.setCarBscMttrInqFlnm(step4OwnerName); + existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); + existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); + existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", "")); + existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord)); + existingData.setRmrk(rmrk); + + int updateCount = carFfnlgTrgtMapper.update(existingData); + if (updateCount == 0) { + throw new MessageException(String.format("[내사종결-명의이전] 업데이트 실패: %s", vhclno)); + } + + log.info("[내사종결-명의이전] 처리 완료! 차량번호: {}", vhclno); + return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; + + } catch (Exception e) { + log.error("[내사종결-명의이전] 검증 중 오류 발생 - 차량번호: {}", vhclno, e); + throw new MessageException(String.format("[내사종결-명의이전] 검증 중 오류 발생 - 차량번호: %s", vhclno), e); + } + } + + /** + * 4. 날짜 수정 후 부과 검증 - 명의이전 이전소유자 상품용, 31일 초과 * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 @@ -668,15 +876,15 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co return null; } - // 조건: 가장 마지막 명의이전일자가 검사일의 31일 초과인지 확인 + // 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 초과인지 확인 long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate); - if (daysBetween <= 31) { - log.debug("[날짜수정후부과] 명의이전일자가 검사일의 31일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일", - targetRecord.getChgYmd(), inspYmd, daysBetween); + if (daysBetween <= DAYS_THRESHOLD) { + log.debug("[날짜수정후부과] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween); return null; } - log.info("[날짜수정후부과] 명의이전일자가 검사일의 31일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", - targetRecord.getChgYmd(), inspYmd, daysBetween); + log.info("[날짜수정후부과] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween); String targetChgYmd = targetRecord.getChgYmd(); log.info("[날짜수정후부과] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm()); @@ -742,9 +950,213 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co } } + /** + * 5. 날짜 수정 후 부과 검증 - 명의이전, 31일 초과 + * + * API 호출 3단계: + * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 + * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 + * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List + * + * 비교조건: + * - 1단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기 + * • 유효기간만료일 <= CHG_YMD <= 검사종료일자 + * • CHG_TASK_SE_CD == "11" (명의이전) + * - 2단계: 명의이전일자와 검사일 비교 + * • 명의이전일자 ~ 검사일까지의 일수 계산 + * • 31일 초과인 경우 + * + * @param existingData 과태료 대상 데이터 + * @return 05 (날짜 수정 후 부과) 또는 null (미적용) + */ + private String checkDateModifiedLevyByOwnerChangeOver31Days(CarFfnlgTrgtVO existingData) { + String vhclno = existingData.getVhclno(); + String inspYmd = existingData.getInspYmd(); + String vldPrdExpryYmd = existingData.getVldPrdExpryYmd(); + String inspEndYmd = existingData.getInspEndYmd(); + + try { + // ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ========== + log.info("[날짜수정후부과-명의이전] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd); + + NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd); + NewBasicResponse step1Response = apiService.getBasicInfo(step1Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId()); + + if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) { + log.warn("[날짜수정후부과-명의이전] Step 1 응답 없음 - 차량번호: {}", vhclno); + return null; + } + + NewBasicResponse.Record step1Record = step1Response.getRecord().get(0); + String vin = step1Record.getVin(); // 차대번호 + String step1OwnerName = step1Record.getRprsOwnrNm(); // 검사일 기준 소유자명 + + log.info("[날짜수정후부과-명의이전] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName); + + // 조건: 소유자명에 "상품용" 미포함 여부 확인 (명의이전 케이스는 상품용이 아니어야 함) + if (step1OwnerName != null && step1OwnerName.contains("상품용")) { + log.debug("[날짜수정후부과-명의이전] 소유자명에 '상품용' 포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + return null; + } + + log.info("[날짜수정후부과-명의이전] 소유자명에 '상품용' 미포함 확인 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); + + // ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ========== + String today = LocalDate.now().format(DATE_FORMATTER); + log.info("[날짜수정후부과-명의이전] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today); + + NewBasicRequest step2Request = createBasicRequest(null, vin, today); + NewBasicResponse step2Response = apiService.getBasicInfo(step2Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtId()); + + if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) { + log.warn("[날짜수정후부과-명의이전] Step 2 응답 없음 - 차대번호: {}", vin); + return null; + } + + NewBasicResponse.Record step2Record = step2Response.getRecord().get(0); + String currentVhclno = step2Record.getVhrno(); + String currentOwnerName = step2Record.getRprsOwnrNm(); + String currentIdecno = step2Record.getRprsvOwnrIdecno(); + String currentLegalDongCode = step2Record.getUsgsrhldStdgCd(); + + log.info("[날짜수정후부과-명의이전] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", + currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + + // ========== Step 3: 자동차등록원부(갑) 조회 ========== + log.info("[날짜수정후부과-명의이전] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", + currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + + NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); + NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request); + ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtId()); + + if (step3Response == null) { + log.warn("[날짜수정후부과-명의이전] Step 3 응답 없음 - 차량번호: {}", currentVhclno); + return null; + } + + List ledgerRecords = step3Response.getRecord(); + if (ledgerRecords == null || ledgerRecords.isEmpty()) { + log.debug("[날짜수정후부과-명의이전] 갑부 상세 내역 없음 - 차량번호: {}", vhclno); + return null; + } + + // ========== 갑부 상세에서 검사기간 내 명의이전 레코드 찾기 ========== + log.info("[날짜수정후부과-명의이전] 갑부 상세 레코드 검색 시작 - 유효기간만료일: {}, 검사종료일자: {}", vldPrdExpryYmd, inspEndYmd); + + NewLedgerResponse.Record targetRecord = null; + LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd); + LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd); + + for (NewLedgerResponse.Record record : ledgerRecords) { + String chgYmd = record.getChgYmd(); + String chgTaskSeCd = record.getChgTaskSeCd(); + + // 조건: CHG_TASK_SE_CD == "11" (명의이전) + if (!"11".equals(chgTaskSeCd) || chgYmd == null) { + continue; + } + + LocalDate chgDate = DateUtil.parseDate(chgYmd); + if (chgDate == null) { + continue; + } + + // 조건: 유효기간만료일 <= CHG_YMD <= 검사종료일자 + if ((chgDate.isEqual(vldPrdExpryDate) || chgDate.isAfter(vldPrdExpryDate)) && + (chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) { + targetRecord = record; + log.debug("[날짜수정후부과-명의이전] 검사기간 내 명의이전 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd); + break; // 첫 번째 발견된 명의이전 사용 + } + } + + if (targetRecord == null) { + log.debug("[날짜수정후부과-명의이전] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno); + return null; + } + + String targetChgYmd = targetRecord.getChgYmd(); + log.info("[날짜수정후부과-명의이전] 검사기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm()); + + // ========== 명의이전일자 ~ 검사일 사이의 일수 계산 ========== + LocalDate chgDate = DateUtil.parseDate(targetChgYmd); + LocalDate inspDate = DateUtil.parseDate(inspYmd); + long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, inspDate); + + if (daysBetween <= DAYS_THRESHOLD) { + log.debug("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween); + return null; + } + + log.info("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일", + DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween); + + // ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD -1일) ========== + LocalDate targetDate = DateUtil.parseDate(targetChgYmd); + String targetChgYmdMinus1 = targetDate.minusDays(1).format(DATE_FORMATTER); + log.info("[날짜수정후부과-명의이전] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {} (원본: {})", vin, targetChgYmdMinus1, targetChgYmd); + + NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmdMinus1); + NewBasicResponse step4Response = apiService.getBasicInfo(step4Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtId()); + + if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) { + log.warn("[날짜수정후부과-명의이전] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmdMinus1); + return null; + } + + NewBasicResponse.Record step4Record = step4Response.getRecord().get(0); + String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명 + + log.info("[날짜수정후부과-명의이전] Step 4 결과 - 소유자명: {}", step4OwnerName); + + // ========== 소유자명 비교 - 명의이전 전 소유자가 상품용이어야 함 ========== + if (step4OwnerName == null || step4OwnerName.contains("상품용")) { + log.debug("[날짜수정후부과-명의이전] 명의이전 전 소유자명에 '상품용' 포함 - Step4 소유자명: {}", step4OwnerName); + return null; + } + + log.info("[날짜수정후부과-명의이전] 명의이전 - 소유자명: {}", step4OwnerName); + + // ========== 비고 생성 ========== + String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark( + step1Record, step4Record, targetRecord, + vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween + ); + + // ========== DB 업데이트 ========== + existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); + existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); + existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY); + existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER)); + existingData.setCarBscMttrInqFlnm(step4OwnerName); + existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); + existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); + existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", "")); + existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord)); + existingData.setRmrk(rmrk); + + int updateCount = carFfnlgTrgtMapper.update(existingData); + if (updateCount == 0) { + throw new MessageException(String.format("[날짜수정후부과-명의이전] 업데이트 실패: %s", vhclno)); + } + + log.info("[날짜수정후부과-명의이전] 처리 완료! 차량번호: {}", vhclno); + return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY; + + } catch (Exception e) { + log.error("[날짜수정후부과-명의이전] 검증 중 오류 발생 - 차량번호: {}", vhclno, e); + throw new MessageException(String.format("[날짜수정후부과-명의이전] 검증 중 오류 발생 - 차량번호: %s", vhclno), e); + } + } + /** - * 4. 이첩 검증 + * 6. 이첩 검증 * * DAYCNT 기반 부과기준일 계산: * - DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일)