# 자동차 과태료 비교 로직 명세서 ## 개요 자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다. ### 구현 위치 - **서비스**: `ComparisonServiceImpl.java` - **경로**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java` ### 기본 설정 - 비교로직에 사용되는 API는 `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출 - 날짜 유틸리티: `DateUtil.parseDate()`, `DateUtil.formatDateString()` 사용 - 일수 계산 기준: `DAYS_THRESHOLD = 31일` ### 문서 이력 | 일자 | 변경 내용 | 비고 | |------|----------|------| | 2025-12-03 | 실제 소스 코드 기준으로 전면 재작성 | Javadoc과 실제 로직 차이 명시 | | 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 31일 이내 조건 추가 | | 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 | ### 처리 규칙 > **중요**: 순서가 중요함! > 1. 상품용 → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 날짜수정(상품용) → 5. 날짜수정(명의이전) → 6. 이첩 > - 조건에 걸리는 순간 다음 차량번호 비교로 진행 > - 각 비교 로직별로 개별 API 호출 수행 --- ## 비교 로직 상세 ### 1. 상품용 검증 - 검사일 소유자가 상품용 **처리상태코드**: `02` (상품용) **메서드**: `checkProductUse()` #### Javadoc 주석 내용 ```java /** * 1. 상품용 검증 api-1번호출.소유자명.contains("상품용") * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD) → 해당 시점의 소유자명 * * 비교조건: * - 1단계: 소유자명에 "상품용" 포함 * - 2단계: 갑부 상세에서 다음 조건을 만족하는 명의이전(코드 11) 레코드 찾기 * • CHG_YMD <= 검사종료일자 * • 현재 주석처리 CHG_YMD가 검사일 이전일자 중 가장 마지막 일자 * • CHG_TASK_SE_CD == "11" (명의이전) * - 3단계: 4번 API 조회 소유자명 == 1번 API 조회 소유자명 -> 소유자 대표 회원 번호 * - 4단계 현재 주석처리 : CHG_YMD >= 유효기간만료일 && CHG_YMD <= 검사종료일자, 현재 주석처리 * CHG_YMD가 검사일 이전일자 중 가장 마지막 일자, 4단계 2개의 조건은 주석처리 */ ``` #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호(vin)`, `소유자명(step1OwnerName)` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=조건에맞는CHG_YMD` | `소유자명(step4OwnerName)` | 명의이전 시점 소유자 확인 | #### 실제 비교 조건 로직 (소스코드 기준) ```java // 조건 1: Step 1 소유자명에 "상품용" 포함 여부 if (step1OwnerName == null || !step1OwnerName.contains("상품용")) { log.debug("[상품용] 검사일 소유자명에 '상품용' 미포함 - 차량번호: {}", vhclno); return null; // 미충족 } // 조건 2: Step 3 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 NewLedgerResponse.Record targetRecord = null; LocalDate latestChgDate = null; LocalDate inspDate = DateUtil.parseDate(inspYmd); 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 <= 검사종료일자 LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd); if (chgDate.isAfter(inspEndDate)) { log.debug("[상품용] CHG_YMD > 검사종료일자 - 변경일자: {}, 검사종료일자: {}", chgYmd, inspEndYmd); continue; } // 필요없음. 조건: CHG_YMD가 검사일 이전일자 중 가장 마지막 일자, 필요없음. /* if (chgDate.isAfter(inspDate)) { log.debug("[상품용] CHG_YMD > 검사일 - 변경일자: {}, 검사일: {}", chgYmd, inspYmd); continue; } */ // 가장 마지막 일자 찾기 if (latestChgDate == null || chgDate.isAfter(latestChgDate)) { latestChgDate = chgDate; targetRecord = record; log.debug("[상품용] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd); } } if (targetRecord == null) { log.debug("[상품용] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno); return null; } // 조건 3: Step 4 소유자명 == Step 1 소유자명 (명의이전 시점 소유자명 == 검사일 기준 소유자명) if (step4OwnerName == null || !step4RprsvOwnrIdecno.equals(step1RprsvOwnrIdecno)) { log.debug("[상품용] 소유자명 불일치 - Step1 소유자: {}, Step4 소유자: {}", step1RprsvOwnrIdecno, step4RprsvOwnrIdecno); return null; } // 주석처리된 조건 4: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내 /* if (!DateUtil.isDateBetween(targetChgYmd, vldPrdExpryYmd, inspEndYmd)) { log.debug("[상품용] CHG_YMD가 유효기간만료일~검사종료일자 범위 밖 - CHG_YMD: {}, 범위: {} ~ {}", targetChgYmd, vldPrdExpryYmd, inspEndYmd); return null; } */ return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE; // "02" (상품용 조건 충족) ``` #### 로직 설명 **검사일 당시 소유자가 상품용이고, 명의이전 시점에도 동일하게 상품용이었던 경우** 1. **검사일 소유자가 상품용**: Step 1 API에서 조회한 소유자명에 "상품용" 포함 2. **명의이전 레코드 검색**: Step 3 갑부 상세에서 다음 조건을 만족하는 레코드 찾기 - `CHG_TASK_SE_CD == "11"` (명의이전) - `CHG_YMD <= 검사종료일자` - 가장 마지막 일자 (여러 개면 최신 날짜) 3. **명의이전 시점 소유자 확인**: Step 4 API로 명의이전 시점(CHG_YMD) 소유자명 조회 4. **소유자명 일치 여부**: Step 1 소유자명 == Step 4 소유자명 #### 결과 처리 - **업무 처리 상태 코드**: `02` (상품용) - **비고 컬럼 형식**: ``` 상품용 - 상품용검사 ``` - 메서드: `ComparisonRemarkBuilder.buildProductUseRemarkCase1()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); existingData.setTaskPrcsSttsCd("02"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(step4OwnerName); // 명의이전 시점 소유자명 existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); // "11" existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); // "명의이전" existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); // 명의이전 변경일자 existingData.setCarRegFrmbkDtl(갑부레코드상세); existingData.setRmrk(비고); ``` --- ### 2. 내사종결 검증 - 명의이전 전 소유자 상품용 (31일 이내) **처리상태코드**: `04` (내사종결) **메서드**: `checkInvestigationClosedByProductUseWithin31Days()` #### Javadoc 주석 내용 ```java /** * 2. 내사종결 검증 - 명의이전 이전소유자 상품용, 31일 이내 * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD - 1 day) → 해당 시점의 소유자명 * * 비교조건: * - 1단계: 소유자명에 "상품용" 이 아닐 경우 포함 * - 2단계: 갑부 상세에서 다음 조건을 만족하는 명의이전(코드 11) 레코드 찾기 * • CHG_YMD <= 검사일자 * • 현재 주석처리 CHG_YMD가 검사일 이전일자 중 가장 마지막 일자 * • CHG_TASK_SE_CD == "11" (명의이전) * • 명의이전일자가 검사일의 31일이내 * - 3단계: 4번 API 조회 !소유자명.contains("상품용") return null, 상품용만 최종 통과 */ ``` #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | #### 실제 비교 조건 로직 (소스코드 기준) ```java // 조건 1: Step 1 소유자명에 "상품용" 미포함 (검사일 당시 상품용 아님) if (step1OwnerName == null || step1OwnerName.contains("상품용")) { log.debug("[내사종결-상품용31일이내] 검사일 소유자가 상품용이거나 null - 차량번호: {}", vhclno); return null; // 상품용은 1번에서 처리됨 } // 조건 2: Step 3 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기 NewLedgerResponse.Record targetRecord = null; LocalDate latestChgDate = null; LocalDate inspDate = DateUtil.parseDate(inspYmd); 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 <= 검사일자 (Javadoc에는 "검사종료일자"라고 되어 있으나 실제는 "검사일자") LocalDate inspEndDate = DateUtil.parseDate(inspYmd); // inspYmd 사용! if (chgDate.isAfter(inspEndDate)) { log.debug("[내사종결-상품용31일이내] CHG_YMD > 검사일 - 변경일자: {}, 검사일: {}", chgYmd, inspYmd); continue; } // 가장 마지막 일자 찾기 if (latestChgDate == null || chgDate.isAfter(latestChgDate)) { latestChgDate = chgDate; targetRecord = record; log.debug("[내사종결-상품용31일이내] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd); } } if (targetRecord == null) { log.debug("[내사종결-상품용31일이내] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno); return null; } // 조건 3: 명의이전일자가 검사일의 31일 이내 long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate); if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) { log.debug("[내사종결-상품용31일이내] 일수 범위 벗어남 - 일수: {}일 (기준: 0~31일)", daysBetween); return null; // 31일 초과 } // 조건 4: Step 4 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 "상품용" 포함 if (step4OwnerName == null || !step4OwnerName.contains("상품용")) { log.debug("[내사종결-상품용31일이내] 명의이전 직전 소유자가 상품용 아님 - 소유자: {}", step4OwnerName); return null; // 명의이전 직전 소유자가 상품용이 아님 } return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; // "04" ``` #### 로직 설명 **검사일 당시에는 상품용이 아니었지만, 명의이전 이전(31일 이내)에는 상품용이었던 경우** 1. **검사일 소유자가 상품용 아님**: Step 1 소유자명에 "상품용" 미포함 2. **검사일 이전 명의이전 발생**: Step 3 갑부 상세에서 `CHG_YMD <= 검사일자`인 명의이전(코드 11) 중 가장 마지막 일자 3. **31일 이내 체크**: 명의이전일자 ~ 검사일 사이 일수가 0~31일 4. **명의이전 직전 소유자가 상품용**: Step 4 API로 `CHG_YMD - 1일` 조회, 소유자명에 "상품용" 포함 #### 결과 처리 - **업무 처리 상태 코드**: `04` (내사종결) - **비고 컬럼 형식**: ``` 명의이전(25.9.3.) 이전소유자 상품용 22루2283 - 검사기간: 2024-08-01 - 2024-08-31 - 검사일: 2024-08-15 - 명의이전: 2024-09-03 - 상품용: 2024-09-03 일수차이: 19일 ``` - 메서드: `ComparisonRemarkBuilder.buildCloseProductUseRemark()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); existingData.setTaskPrcsSttsCd("04"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(step4OwnerName); // 명의이전 직전 소유자명 existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); existingData.setCarRegFrmbkDtl(갑부레코드상세); existingData.setRmrk(비고); ``` --- ### 3. 내사종결 검증 - 순수 명의이전 (31일 이내) **처리상태코드**: `04` (내사종결) **메서드**: `checkInvestigationClosedByOwnerChangeWithin31Days()` #### Javadoc 주석 내용 ```java /** * 3. 내사종결 검증 - 순수 명의이전 (31일 이내) * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List * 4) 자동차기본정보(차대번호, 부과일자=CHG_YMD - 1일) → 명의이전 직전 소유자명 * * 비교조건: * - 1단계: 검사일 소유자명에 "상품용" 미포함 (상품용은 1번에서 처리) * - 2단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기 * • 유효기간만료일 <= CHG_YMD <= 검사종료일자 * • CHG_TASK_SE_CD == "11" (명의이전) * - 3단계: 명의이전일자와 검사일 비교 * • 명의이전일자 ~ 검사일까지의 일수 계산 * • 31일 이내인 경우 * - 4단계: 명의이전 직전 소유자가 상품용 아님 (상품용이면 2번에서 처리됨) */ ``` #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | #### 실제 비교 조건 로직 (소스코드 기준) ```java // 조건 1: 검사일 소유자명에 "상품용" 미포함 if (step1OwnerName != null && step1OwnerName.contains("상품용")) { log.debug("[내사종결-순수명의이전31일이내] 검사일 소유자가 상품용 - 차량번호: {}", vhclno); return null; // 상품용이면 1번에서 처리됨 } // 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기 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("[내사종결-순수명의이전31일이내] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd); break; // 첫 번째 발견된 명의이전 사용 } } if (targetRecord == null) { log.debug("[내사종결-순수명의이전31일이내] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno); return null; } // 조건 3: 명의이전일자가 검사일의 31일 이내 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("[내사종결-순수명의이전31일이내] 일수 범위 벗어남 - 일수: {}일 (기준: 0~31일)", daysBetween); return null; } // 조건 4: 명의이전 직전 소유자가 상품용 아님 (상품용이면 2번에서 처리됨) if (step4OwnerName == null || step4OwnerName.contains("상품용")) { log.debug("[내사종결-순수명의이전31일이내] 명의이전 직전 소유자가 상품용 - 소유자: {}", step4OwnerName); return null; // 상품용이면 이미 2번에서 처리됨 } return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; // "04" ``` #### 로직 설명 **순수 명의이전 케이스 (상품용과 무관)** 1. **검사일 소유자가 상품용 아님**: Step 1 소유자명에 "상품용" 미포함 2. **검사기간 내 명의이전 발생**: Step 3 갑부 상세에서 `유효기간만료일 <= CHG_YMD <= 검사종료일자`인 명의이전(코드 11) 3. **31일 이내 체크**: 명의이전일자 ~ 검사일 사이 일수가 0~31일 4. **명의이전 직전 소유자도 상품용 아님**: Step 4 소유자명에 "상품용" 미포함 (상품용이면 2번 로직에서 처리됨) #### 결과 처리 - **업무 처리 상태 코드**: `04` (내사종결) - **비고 컬럼 형식**: ``` 명의이전(25.9.3.) 22루2283 - 검사기간: 2024-08-01 - 2024-08-31 - 검사일: 2024-08-15 - 명의이전: 2024-09-03 - 상품용: 2024-09-03 일수차이: 19일 ``` - 메서드: `ComparisonRemarkBuilder.buildOwnerChangeRemark()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); existingData.setTaskPrcsSttsCd("04"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(step4OwnerName); // 명의이전 직전 소유자명 existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); existingData.setCarRegFrmbkDtl(갑부레코드상세); existingData.setRmrk(비고); ``` --- ### 4. 날짜 수정 후 부과 검증 - 명의이전 전 소유자 상품용 (31일 초과) **처리상태코드**: `05` (날짜 수정 후 부과) **메서드**: `checkDateModifiedLevyByProductUseOver31Days()` #### Javadoc 주석 내용 ```java /** * 4. 날짜 수정 후 부과 검증 - 명의이전 이전소유자 상품용, 31일 초과 * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD - 1 day) → 해당 시점의 소유자명 * * 비교조건: * - 1단계: 소유자명에 "상품용" 이 아닐 경우 포함 * - 2단계: 갑부 상세에서 다음 조건을 만족하는 명의이전(코드 11) 레코드 찾기 * • CHG_YMD <= 검사일자 * • 현재 주석처리 CHG_YMD가 검사일 이전일자 중 가장 마지막 일자 * • CHG_TASK_SE_CD == "11" (명의이전) * • 명의이전일자가 검사일의 31일 초과 * - 3단계: 4번 API 조회 소유자명.contains("상품용") */ ``` **⚠️ Javadoc과 실제 로직 차이**: - Javadoc: "3단계: 4번 API 조회 소유자명.contains('상품용')" - 실제 로직: 상품용 **포함** 체크 (`step4OwnerName.contains("상품용")`) - Javadoc이 맞음 #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | #### 실제 비교 조건 로직 (소스코드 기준) ```java // 조건 1: 검사일 소유자명에 "상품용" 미포함 if (step1OwnerName == null || step1OwnerName.contains("상품용")) { log.debug("[날짜수정-상품용31일초과] 검사일 소유자가 상품용이거나 null - 차량번호: {}", vhclno); return null; } // 조건 2: 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기 (2번 메서드와 동일) NewLedgerResponse.Record targetRecord = null; LocalDate latestChgDate = null; LocalDate inspDate = DateUtil.parseDate(inspYmd); 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 <= 검사일자 LocalDate inspEndDate = DateUtil.parseDate(inspYmd); if (chgDate.isAfter(inspEndDate)) { continue; } // 가장 마지막 일자 찾기 if (latestChgDate == null || chgDate.isAfter(latestChgDate)) { latestChgDate = chgDate; targetRecord = record; } } if (targetRecord == null) { log.debug("[날짜수정-상품용31일초과] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno); return null; } // 조건 3: 명의이전일자가 검사일의 31일 초과 long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate); if (daysBetween <= DAYS_THRESHOLD) { log.debug("[날짜수정-상품용31일초과] 일수가 31일 이내 - 일수: {}일", daysBetween); return null; // 31일 이내면 2번에서 처리됨 } // 조건 4: Step 4 소유자명에 "상품용" 포함 if (step4OwnerName == null || !step4OwnerName.contains("상품용")) { log.debug("[날짜수정-상품용31일초과] 명의이전 직전 소유자가 상품용 아님 - 소유자: {}", step4OwnerName); return null; } return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY; // "05" ``` #### 로직 설명 **명의이전 전 소유자가 상품용이지만 31일을 초과한 경우** 1. **검사일 소유자가 상품용 아님**: Step 1 소유자명에 "상품용" 미포함 2. **검사일 이전 명의이전 발생**: Step 3 갑부 상세에서 `CHG_YMD <= 검사일자`인 명의이전(코드 11) 중 가장 마지막 일자 3. **31일 초과**: 명의이전일자 ~ 검사일 사이 일수가 31일 초과 4. **명의이전 직전 소유자가 상품용**: Step 4 소유자명에 "상품용" 포함 #### 결과 처리 - **업무 처리 상태 코드**: `05` (날짜 수정 후 부과) - **비고 컬럼 형식**: 2번과 동일 ``` 명의이전(25.9.3.) 이전소유자 상품용 22루2283 - 검사기간: 2024-08-01 - 2024-08-31 - 검사일: 2024-08-15 - 명의이전: 2024-07-01 - 상품용: 2024-07-01 일수차이: 45일 ``` - 메서드: `ComparisonRemarkBuilder.buildCloseProductUseRemark()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); existingData.setTaskPrcsSttsCd("05"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(step4OwnerName); existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); existingData.setCarRegFrmbkDtl(갑부레코드상세); existingData.setRmrk(비고); ``` --- ### 5. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과) **처리상태코드**: `05` (날짜 수정 후 부과) **메서드**: `checkDateModifiedLevyByOwnerChangeOver31Days()` #### Javadoc 주석 내용 ```java /** * 5. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과) * * API 호출 4단계: * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List * 4) 자동차기본정보(차대번호, 부과일자=CHG_YMD - 1일) → 명의이전 직전 소유자명 * * 비교조건: * - 1단계: 검사일 소유자명에 "상품용" 미포함 (상품용은 1번에서 처리) * - 2단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기 * • 유효기간만료일 <= CHG_YMD <= 검사종료일자 * • CHG_TASK_SE_CD == "11" (명의이전) * - 3단계: 명의이전일자와 검사일 비교 * • 명의이전일자 ~ 검사일까지의 일수 계산 * • 31일 초과인 경우 * - 4단계: 명의이전 직전 소유자가 상품용 아님 (상품용이면 4번에서 처리됨) */ ``` #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | #### 실제 비교 조건 로직 (소스코드 기준) ```java // 조건 1: 검사일 소유자명에 "상품용" 미포함 if (step1OwnerName != null && step1OwnerName.contains("상품용")) { log.debug("[날짜수정-순수명의이전31일초과] 검사일 소유자가 상품용 - 차량번호: {}", vhclno); return null; } // 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기 (3번 메서드와 동일) 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; break; } } if (targetRecord == null) { log.debug("[날짜수정-순수명의이전31일초과] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno); return null; } // 조건 3: 명의이전일자 ~ 검사일 사이 31일 초과 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("[날짜수정-순수명의이전31일초과] 일수가 31일 이내 - 일수: {}일", daysBetween); return null; // 31일 이내면 3번에서 처리됨 } // 조건 4: 명의이전 직전 소유자가 상품용 아님 if (step4OwnerName == null || step4OwnerName.contains("상품용")) { log.debug("[날짜수정-순수명의이전31일초과] 명의이전 직전 소유자가 상품용 - 소유자: {}", step4OwnerName); return null; // 상품용이면 4번에서 처리됨 } return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY; // "05" ``` #### 로직 설명 **순수 명의이전 케이스 중 31일을 초과한 경우** 1. **검사일 소유자가 상품용 아님**: Step 1 소유자명에 "상품용" 미포함 2. **검사기간 내 명의이전 발생**: Step 3 갑부 상세에서 `유효기간만료일 <= CHG_YMD <= 검사종료일자`인 명의이전(코드 11) 3. **31일 초과**: 명의이전일자 ~ 검사일 사이 일수가 31일 초과 4. **명의이전 직전 소유자도 상품용 아님**: Step 4 소유자명에 "상품용" 미포함 (상품용이면 4번 로직에서 처리됨) #### 결과 처리 - **업무 처리 상태 코드**: `05` (날짜 수정 후 부과) - **비고 컬럼 형식**: 3번과 동일 ``` 명의이전(25.9.3.) 22루2283 - 검사기간: 2024-08-01 - 2024-08-31 - 검사일: 2024-08-15 - 명의이전: 2024-07-01 - 상품용: 2024-07-01 일수차이: 45일 ``` - 메서드: `ComparisonRemarkBuilder.buildOwnerChangeRemark()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); existingData.setTaskPrcsSttsCd("05"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(step4OwnerName); existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); existingData.setCarRegFrmbkDtl(갑부레코드상세); existingData.setRmrk(비고); ``` --- ### 6. 이첩 검증 (이첩-1, 이첩-2) **처리상태코드**: `03` (이첩) **메서드**: `checkTransferCase115Day()` #### Javadoc 주석 내용 ```java /** * 6. 이첩 검증 * * DAYCNT 기반 부과기준일 계산: * - DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일) * - DAYCNT <= 115: 이첩-1 (부과기준일 = 검사일자) * * 법정동코드 비교: * - 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리 */ ``` #### 부과기준일 결정 ```java String daycntStr = existingData.getDaycnt(); if (daycntStr == null || daycntStr.isEmpty()) { log.warn("[이첩] DAYCNT 값이 없음 - 차량번호: {}", vhclno); return null; } int daycnt = Integer.parseInt(daycntStr); String levyCrtrYmd; // 부과기준일 String transferType; if (daycnt > 115) { // 이첩-2: 부과기준일 = 검사종료일자 + 115일 String inspEndYmd = existingData.getInspEndYmd(); LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd); LocalDate levyCrtrDate = inspEndDate.plusDays(115); levyCrtrYmd = levyCrtrDate.format(DATE_FORMATTER); transferType = "이첩-2"; log.info("[이첩-2] DAYCNT > 115 - 부과기준일 = 검사종료일자({}) + 115일 = {}", inspEndYmd, levyCrtrYmd); } else { // 이첩-1: 부과기준일 = 검사일자 levyCrtrYmd = existingData.getInspYmd(); transferType = "이첩-1"; log.info("[이첩-1] DAYCNT <= 115 - 부과기준일 = 검사일자({})", levyCrtrYmd); } ``` #### API 호출 | API | 입력 파라미터 | 출력 데이터 | 용도 | |-----|--------------|-------------|------| | 자동차기본정보 | `차량번호`, `부과일자=부과기준일` | `사용본거지법정동코드(usgsrhldStdgCd)` | 부과기준일 기준 사용본거지 확인 | #### 법정동코드 비교 ```java // API 호출 NewBasicRequest request = createBasicRequest(vhclno, null, levyCrtrYmd); NewBasicResponse response = apiService.getBasicInfo(request); bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(response, existingData.getCarFfnlgTrgtId()); // 사용본거지법정동코드 추출 String usgsrhldStdgCd = record.getUsgsrhldStdgCd(); if (usgsrhldStdgCd == null || usgsrhldStdgCd.length() < 4) { log.warn("[이첩] 법정동코드 유효하지 않음 - 차량번호: {}, 법정동코드: {}", vhclno, usgsrhldStdgCd); return null; } // 세션에서 사용자 정보 조회 LoginUserVO userInfo = SessionUtil.getLoginUser(); if (userInfo == null || userInfo.getOrgCd() == null) { log.warn("[이첩] 사용자 정보 없음 - 차량번호: {}", vhclno); return null; } // 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교 String legalDong4 = usgsrhldStdgCd.substring(0, 4); String userOrgCd = userInfo.getOrgCd(); String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd; if (legalDong4.equals(userOrg4)) { log.debug("[이첩] 법정동코드 일치 - 법정동: {}, 조직코드: {}", legalDong4, userOrg4); return null; // 일치하면 이첩 대상 아님 } log.info("[이첩] 법정동코드 불일치 - 법정동: {}, 조직코드: {}", legalDong4, userOrg4); // 시군구 코드 및 시군구명 조회 String sggCd = usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd; String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd); return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER; // "03" ``` #### 결과 처리 | 구분 | 조건 | 비고 컬럼 형식 | |------|------|---------------| | 이첩-1 | `DAYCNT <= 115` | `"{시군구명}, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리: {userOrg4}, 법정동명: {sggNm}]"` | | 이첩-2 | `DAYCNT > 115` | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` | - 메서드: `ComparisonRemarkBuilder.buildTransferRemark()` #### DB 업데이트 필드 ```java existingData.setCarBassMatterInqireId(response.getGeneratedId()); existingData.setTaskPrcsSttsCd("03"); existingData.setTaskPrcsYmd(오늘일자); existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm()); // 기존 소유자명 유지 existingData.setCarBscMttrInqSggCd(sggCd); // 시군구 코드 existingData.setCarBscMttrInqSggNm(sggNm); // 시군구명 existingData.setRmrk(비고); ``` --- ## 처리 흐름도 ``` 시작 │ ▼ [차량번호 조회] │ ▼ ┌─────────────────────────────────────┐ │ 1. 상품용 (검사일 소유자가 상품용) │ │ → 조건 충족: 상품용(02) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ ┌─────────────────────────────────────┐ │ 2. 내사종결 - 명의이전 전 상품용 │ │ (31일 이내) │ │ → 조건 충족: 내사종결(04) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ ┌─────────────────────────────────────┐ │ 3. 내사종결 - 순수 명의이전 │ │ (31일 이내, 상품용 아님) │ │ → 조건 충족: 내사종결(04) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ ┌─────────────────────────────────────┐ │ 4. 날짜수정 - 명의이전 전 상품용 │ │ (31일 초과) │ │ → 조건 충족: 날짜수정후부과(05) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ ┌─────────────────────────────────────┐ │ 5. 날짜수정 - 순수 명의이전 │ │ (31일 초과, 상품용 아님) │ │ → 조건 충족: 날짜수정후부과(05) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ ┌─────────────────────────────────────┐ │ 6. 이첩 (법정동코드 불일치) │ │ → 조건 충족: 이첩(03) │ └─────────────────────────────────────┘ │ (조건 미충족) ▼ [다음 차량] ``` --- ## 요약 정리 ### 비교 로직 실행 순서 ```java public String executeComparison(CarFfnlgTrgtVO existingData) { // 1. 상품용 String result1 = checkProductUse(existingData); if (result1 != null) return result1; // "02" // 2. 내사종결 - 명의이전 전 소유자 상품용 (31일 이내) String result2 = checkInvestigationClosedByProductUseWithin31Days(existingData); if (result2 != null) return result2; // "04" // 3. 내사종결 - 순수 명의이전 (31일 이내) String result3 = checkInvestigationClosedByOwnerChangeWithin31Days(existingData); if (result3 != null) return result3; // "04" // 4. 날짜 수정 후 부과 - 명의이전 전 소유자 상품용 (31일 초과) String result4 = checkDateModifiedLevyByProductUseOver31Days(existingData); if (result4 != null) return result4; // "05" // 5. 날짜 수정 후 부과 - 순수 명의이전 (31일 초과) String result5 = checkDateModifiedLevyByOwnerChangeOver31Days(existingData); if (result5 != null) return result5; // "05" // 6. 이첩 String result6 = checkTransferCase115Day(existingData); if (result6 != null) return result6; // "03" // 모든 조건 미충족 return null; } ``` ### 처리상태코드 매핑 | 코드 | 상태명 | 처리 로직 | 메서드 | |------|--------|----------|--------| | 02 | 상품용 | 검사일 소유자가 상품용 | `checkProductUse()` | | 04 | 내사종결 | 명의이전 전 상품용 (31일 이내) | `checkInvestigationClosedByProductUseWithin31Days()` | | 04 | 내사종결 | 순수 명의이전 (31일 이내) | `checkInvestigationClosedByOwnerChangeWithin31Days()` | | 05 | 날짜 수정 후 부과 | 명의이전 전 상품용 (31일 초과) | `checkDateModifiedLevyByProductUseOver31Days()` | | 05 | 날짜 수정 후 부과 | 순수 명의이전 (31일 초과) | `checkDateModifiedLevyByOwnerChangeOver31Days()` | | 03 | 이첩 | 법정동코드 불일치 | `checkTransferCase115Day()` | ### 상품용 vs 순수 명의이전 비교 | 구분 | 상품용 케이스 (2, 4번) | 순수 명의이전 케이스 (3, 5번) | |------|----------------------|----------------------------| | 검사일 소유자 | 상품용 아님 | 상품용 아님 | | 명의이전 직전 소유자 | 상품용 **포함** | 상품용 **아님** | | Step 4 확인사항 | 상품용 포함 여부 | 상품용 미포함 여부 (상품용이면 이미 2, 4번에서 처리) | | 일수 기준 | 31일 이내/초과 | 31일 이내/초과 | | 처리상태코드 | 31일 이내: 04, 초과: 05 | 31일 이내: 04, 초과: 05 | --- ## Javadoc 주석과 실제 로직 차이점 ### 1. checkInvestigationClosedByProductUseWithin31Days() (2번 메서드) **Javadoc 주석**: ``` - 3단계: 4번 API 조회 소유자명.contains("상품용") ``` **실제 로직**: ```java if (step4OwnerName == null || !step4OwnerName.contains("상품용")) { return null; // 상품용 미포함 체크 } ``` **차이점**: Javadoc은 "상품용 포함"이라고 되어 있으나, 실제 로직은 "상품용 **미포함**" 체크 ### 2. 날짜 비교 기준 (2번, 4번 메서드) **Javadoc 주석**: ``` • CHG_YMD <= 검사일자 ``` **실제 변수명**: ```java LocalDate inspEndDate = DateUtil.parseDate(inspYmd); // inspYmd 사용! ``` **차이점**: 변수명은 `inspEndDate`지만 실제로는 `inspYmd` (검사일자)를 파싱함 --- ## 구현 완료 상태 ### ✅ 1. 상품용 로직 - **메서드**: `checkProductUse()` - **처리상태코드**: 02 ### ✅ 2. 내사종결 - 명의이전 전 소유자 상품용 (31일 이내) - **메서드**: `checkInvestigationClosedByProductUseWithin31Days()` - **처리상태코드**: 04 ### ✅ 3. 내사종결 - 순수 명의이전 (31일 이내) - **메서드**: `checkInvestigationClosedByOwnerChangeWithin31Days()` - **처리상태코드**: 04 - **특징**: 명의이전 직전 소유자도 상품용 아님 ### ✅ 4. 날짜 수정 후 부과 - 명의이전 전 소유자 상품용 (31일 초과) - **메서드**: `checkDateModifiedLevyByProductUseOver31Days()` - **처리상태코드**: 05 ### ✅ 5. 날짜 수정 후 부과 - 순수 명의이전 (31일 초과) - **메서드**: `checkDateModifiedLevyByOwnerChangeOver31Days()` - **처리상태코드**: 05 - **특징**: 명의이전 직전 소유자도 상품용 아님 ### ✅ 6. 이첩 로직 - **메서드**: `checkTransferCase115Day()` - **처리상태코드**: 03 --- ## 구현 시 주의사항 ### 공통 주의사항 1. **API 호출 순서 준수**: 각 검증 단계별로 필요한 API만 호출 2. **조건 우선순위**: 1 → 2 → 3 → 4 → 5 → 6 순서로 검증 3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동 4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록 5. **날짜 형식 지원**: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 6. **일수 계산 기준**: DAYS_THRESHOLD = 31일 (상수 사용) ### 순수 명의이전 케이스 (3, 5번) 주의사항 1. **상품용 체크**: Step 4에서 명의이전 직전 소유자가 상품용이면 return null - 상품용이면 이미 2번 또는 4번에서 처리되어야 함 2. **로직 순서 중요**: 상품용 케이스(2, 4번)보다 반드시 뒤에 실행 3. **DB 저장값**: step4OwnerName을 CAR_BSC_MTTR_INQ_FLNM에 저장 ### Javadoc과 실제 로직 차이 1. **2번 메서드**: Javadoc에는 "상품용 포함"이라고 되어 있으나 실제는 "상품용 미포함" 체크 2. **날짜 비교**: inspEndDate 변수명을 사용하지만 실제로는 inspYmd (검사일자) 파싱 --- ## 참고 상수 및 유틸리티 ### 상수 ```java // 일수 기준값 private static final int DAYS_THRESHOLD = 31; // 처리상태코드 TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE = "02" TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER = "03" TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED = "04" TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY = "05" // 변경업무구분코드 "11" = 명의이전 ``` ### 유틸리티 ```java // 날짜 파싱 (yyyyMMdd, yyyy-MM-dd 지원) LocalDate DateUtil.parseDate(String dateStr) // 일수 계산 long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) // 날짜 포맷 String DateUtil.formatDateString(String date, String format) ``` --- **문서 작성 완료일**: 2025-12-03 **실제 소스 코드 기준**: ComparisonServiceImpl.java **분석 대상 메서드**: 6개 (상품용, 내사종결 2개, 날짜수정 2개, 이첩)