From c85deef5ee0a051d7d50fb12a639a11d27c97b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EC=98=81?= Date: Mon, 8 Dec 2025 13:41:18 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=80=EC=97=B0=20-=20=EB=B9=84=EA=B5=90?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/자동차과태료_비교로직_정리-[지연].md | 419 ++++++++ docs/자동차과태료_비교로직_정리.md | 1102 --------------------- 2 files changed, 419 insertions(+), 1102 deletions(-) create mode 100644 docs/자동차과태료_비교로직_정리-[지연].md delete mode 100644 docs/자동차과태료_비교로직_정리.md diff --git a/docs/자동차과태료_비교로직_정리-[지연].md b/docs/자동차과태료_비교로직_정리-[지연].md new file mode 100644 index 0000000..c0cd051 --- /dev/null +++ b/docs/자동차과태료_비교로직_정리-[지연].md @@ -0,0 +1,419 @@ +# 자동차 과태료 비교 로직 명세서 (지연) + +## 개요 + +자동차 과태료 부과 대상(지연)을 검증하기 위한 비교 로직 정의서입니다. + +### 구현 위치 +- **Checker 클래스**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/delay_checker/` + - `ProductUseChecker.java` - 1. 상품용 + - `ProductCloseWithin31Checker.java` - 2. 내사종결(명의이전 이전소유자 상품용, 31일 이내) + - `OwnerCloseWithin31Checker.java` - 3. 내사종결(순수 명의이전, 31일 이내) + - `ProductLevyOver31Checker.java` - 4. 날짜수정후부과(명의이전 이전소유자 상품용, 31일 초과) + - `OwnerLevyOver31Checker.java` - 5. 날짜수정후부과(순수 명의이전, 31일 초과) + - `TransferCase115DayChecker.java` - 6. 이첩 + +### 기본 설정 +- 비교로직에 사용되는 API: `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출 +- 날짜 유틸리티: `DateUtil.parseDate()`, `DateUtil.formatDateString()` 사용 +- 일수 계산 기준: `DAYS_THRESHOLD = 31일` + +### 문서 이력 + +| 일자 | 변경 내용 | 비고 | +|------|----------|------| +| 2025-12-08 | delay_checker 소스 기준 전면 재작성 | 실제 코드와 일치하도록 정리 | +| 2025-12-03 | 실제 소스 코드 기준으로 전면 재작성 | Javadoc과 실제 로직 차이 명시 | +| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 31일 이내 조건 추가 | + +### 처리 규칙 + +> **중요**: 순서가 중요함! +> 1. 상품용 → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 날짜수정(상품용) → 5. 날짜수정(명의이전) → 6. 이첩 +> - 조건에 걸리는 순간 다음 차량번호 비교로 진행 +> - 각 비교 로직별로 개별 API 호출 수행 + +--- + +## 비교 로직 상세 + +### 1. 상품용 검증 (`ProductUseChecker`) + +**처리상태코드**: `02` (상품용) +**클래스**: `ProductUseChecker.java` + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호(vin)`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 | + +#### 비교 조건 + +``` +1. Step1 소유자명.contains("상품용") ← 상품용 아니면 return null + +2. 갑부에서 명의이전(11) 레코드 찾기 + - CHG_TASK_SE_CD == "11" + - CHG_YMD <= 검사종료일자 + - 가장 마지막 일자 선택 + ← 없으면 return null + +3. Step4 대표소유자회원번호 == Step1 대표소유자회원번호 + ← 불일치면 return null + +→ 모든 조건 충족: 상품용(02) 처리 +``` + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("02"); // 상품용 +existingData.setCarBscMttrInqFlnm(step4OwnerName); +existingData.setCarRegFrmbkChgTaskSeCd("11"); +existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); +``` + +--- + +### 2. 내사종결 - 명의이전 이전소유자 상품용, 31일 이내 (`ProductCloseWithin31Checker`) + +**처리상태코드**: `04` (내사종결) +**클래스**: `ProductCloseWithin31Checker.java` + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | + +#### 비교 조건 + +``` +1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨) + !Step1 소유자명.contains("상품용") → 계속 진행 + +2. 갑부에서 명의이전(11) 레코드 찾기 + - CHG_TASK_SE_CD == "11" + - CHG_YMD <= 검사일 + - 가장 마지막 일자 선택 + ← 없으면 return null + +3. 일수 계산: 명의이전일자 ~ 검사일 + - 0~31일 이내 → 계속 진행 + - 31일 초과 → return null (4번에서 처리) + +4. Step4 소유자명.contains("상품용") + ← 상품용 아니면 return null + +→ 모든 조건 충족: 내사종결(04) 처리 +``` + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("04"); // 내사종결 +existingData.setCarBscMttrInqFlnm(step4OwnerName); // 명의이전 직전 소유자명 +existingData.setCarRegFrmbkChgTaskSeCd("11"); +existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); +``` + +--- + +### 3. 내사종결 - 순수 명의이전, 31일 이내 (`OwnerCloseWithin31Checker`) + +**처리상태코드**: `04` (내사종결) +**클래스**: `OwnerCloseWithin31Checker.java` + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 | + +#### 비교 조건 + +``` +1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨) + !Step1 소유자명.contains("상품용") → 계속 진행 + +2. 갑부에서 명의이전(11) 레코드 찾기 + - CHG_TASK_SE_CD == "11" + - 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자 + - 가장 마지막 일자 선택 + ← 없으면 return null + +3. 일수 계산: 명의이전일자 ~ 검사일 + - 0~31일 이내 → 계속 진행 + - 31일 초과 → return null (5번에서 처리) + +4. Step4 대표소유자회원번호 == Step1 대표소유자회원번호 + ← 불일치면 return null + +→ 모든 조건 충족: 내사종결(04) 처리 +``` + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("04"); // 내사종결 +existingData.setCarBscMttrInqFlnm(step4OwnerName); +existingData.setCarRegFrmbkChgTaskSeCd("11"); +existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); +``` + +--- + +### 4. 날짜수정후부과 - 명의이전 이전소유자 상품용, 31일 초과 (`ProductLevyOver31Checker`) + +**처리상태코드**: `05` (날짜 수정 후 부과) +**클래스**: `ProductLevyOver31Checker.java` + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | + +#### 비교 조건 + +``` +1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨) + !Step1 소유자명.contains("상품용") → 계속 진행 + +2. 갑부에서 명의이전(11) 레코드 찾기 + - CHG_TASK_SE_CD == "11" + - CHG_YMD <= 검사일 + - 가장 마지막 일자 선택 + ← 없으면 return null + +3. 일수 계산: 명의이전일자 ~ 검사일 + - 31일 초과 → 계속 진행 + - 0~31일 이내 → return null (2번에서 처리) + +4. Step4 소유자명.contains("상품용") + ← 상품용 아니면 return null + +→ 모든 조건 충족: 날짜수정후부과(05) 처리 +``` + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("05"); // 날짜수정후부과 +existingData.setCarBscMttrInqFlnm(step4OwnerName); // 명의이전 직전 소유자명 +existingData.setCarRegFrmbkChgTaskSeCd("11"); +existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); +``` + +--- + +### 5. 날짜수정후부과 - 순수 명의이전, 31일 초과 (`OwnerLevyOver31Checker`) + +**처리상태코드**: `05` (날짜 수정 후 부과) +**클래스**: `OwnerLevyOver31Checker.java` + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 | + +#### 비교 조건 + +``` +1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨) + !Step1 소유자명.contains("상품용") → 계속 진행 + +2. 갑부에서 명의이전(11) 레코드 찾기 + - CHG_TASK_SE_CD == "11" + - 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자 + - 가장 마지막 일자 선택 + ← 없으면 return null + +3. 일수 계산: 명의이전일자 ~ 검사일 + - 31일 초과 → 계속 진행 + - 0~31일 이내 → return null (3번에서 처리) + +4. Step4 대표소유자회원번호 == Step1 대표소유자회원번호 + ← 불일치면 return null + +→ 모든 조건 충족: 날짜수정후부과(05) 처리 +``` + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("05"); // 날짜수정후부과 +existingData.setCarBscMttrInqFlnm(step4OwnerName); +existingData.setCarRegFrmbkChgTaskSeCd("11"); +existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); +``` + +--- + +### 6. 이첩 검증 (`TransferCase115DayChecker`) + +**처리상태코드**: `03` (이첩) +**클래스**: `TransferCase115DayChecker.java` + +#### API 호출 + +| API | 입력 파라미터 | 출력 데이터 | 용도 | +|-----|--------------|-------------|------| +| 자동차기본정보 | `차량번호`, `부과일자=부과기준일` | `사용본거지법정동코드` | 부과기준일 기준 사용본거지 확인 | + +#### 비교 조건 + +``` +1. DAYCNT 값으로 부과기준일 계산 + - DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일) + - DAYCNT <= 115: 이첩-1 (부과기준일 = 검사일자) + ← DAYCNT 없으면 return null + +2. 자동차기본정보(차량번호, 부과기준일) 호출 + → 사용본거지법정동코드 조회 + +3. 법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리 + ← 일치하면 return null + +→ 불일치: 이첩(03) 처리 +``` + +#### 결과 처리 + +| 구분 | 조건 | 비고 컬럼 형식 | +|------|------|---------------| +| 이첩-1 | `DAYCNT <= 115` | `"{시군구명}, 검사일사용본거지, [검사대상, 사용자 조직코드: {userOrg4}, 법정동명: {sggNm}]"` | +| 이첩-2 | `DAYCNT > 115` | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` | + +#### DB 업데이트 필드 + +```java +existingData.setTaskPrcsSttsCd("03"); // 이첩 +existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm()); // 기존 소유자명 유지 +existingData.setCarBscMttrInqSggCd(sggCd); +existingData.setCarBscMttrInqSggNm(sggNm); +``` + +--- + +## 처리 흐름도 + +``` +시작 + │ + ▼ +[차량번호 조회] + │ + ▼ +┌──────────────────────────────────────────────┐ +│ 1. 상품용 (ProductUseChecker) │ +│ 조건: 검사일 소유자명.contains("상품용") │ +│ → 조건 충족: 상품용(02) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +┌──────────────────────────────────────────────┐ +│ 2. 내사종결-상품용 (ProductCloseWithin31Checker) │ +│ 조건: 31일 이내 + 명의이전 전 상품용 │ +│ → 조건 충족: 내사종결(04) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +┌──────────────────────────────────────────────┐ +│ 3. 내사종결-명의이전 (OwnerCloseWithin31Checker) │ +│ 조건: 31일 이내 + 동일 소유자 │ +│ → 조건 충족: 내사종결(04) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +┌──────────────────────────────────────────────┐ +│ 4. 날짜수정-상품용 (ProductLevyOver31Checker) │ +│ 조건: 31일 초과 + 명의이전 전 상품용 │ +│ → 조건 충족: 날짜수정후부과(05) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +┌──────────────────────────────────────────────┐ +│ 5. 날짜수정-명의이전 (OwnerLevyOver31Checker) │ +│ 조건: 31일 초과 + 동일 소유자 │ +│ → 조건 충족: 날짜수정후부과(05) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +┌──────────────────────────────────────────────┐ +│ 6. 이첩 (TransferCase115DayChecker) │ +│ 조건: 법정동코드 앞4자리 != 조직코드 앞4자리│ +│ → 조건 충족: 이첩(03) │ +└──────────────────────────────────────────────┘ + │ (조건 미충족) + ▼ +[다음 차량] +``` + +--- + +## 요약 정리 + +### 처리상태코드 매핑 + +| 코드 | 상태명 | 처리 로직 | 클래스 | +|------|--------|----------|--------| +| 02 | 상품용 | 검사일 소유자가 상품용 | `ProductUseChecker` | +| 04 | 내사종결 | 명의이전 전 상품용 (31일 이내) | `ProductCloseWithin31Checker` | +| 04 | 내사종결 | 순수 명의이전 (31일 이내) | `OwnerCloseWithin31Checker` | +| 05 | 날짜수정후부과 | 명의이전 전 상품용 (31일 초과) | `ProductLevyOver31Checker` | +| 05 | 날짜수정후부과 | 순수 명의이전 (31일 초과) | `OwnerLevyOver31Checker` | +| 03 | 이첩 | 법정동코드 불일치 | `TransferCase115DayChecker` | + +### 상품용 vs 순수 명의이전 비교 + +| 구분 | 상품용 케이스 (2, 4번) | 순수 명의이전 케이스 (3, 5번) | +|------|----------------------|----------------------------| +| 검사일 소유자 | 상품용 아님 | 상품용 아님 | +| 명의이전 직전 소유자 | 상품용 **포함** | 소유자 회원번호 **일치** | +| Step 4 확인사항 | 소유자명.contains("상품용") | 대표소유자회원번호 == Step1 | +| 갑부 검색 조건 | CHG_YMD <= 검사일 | 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자 | +| 일수 기준 | 31일 이내/초과 | 31일 이내/초과 | +| 처리상태코드 | 31일 이내: 04, 초과: 05 | 31일 이내: 04, 초과: 05 | + +--- + +## 참고 상수 및 유틸리티 + +### 상수 +```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" = 명의이전 +``` + +--- + +**문서 작성 완료일**: 2025-12-08 +**실제 소스 코드 기준**: delay_checker 폴더 내 Checker 클래스들 +**분석 대상 클래스**: 6개 (ProductUseChecker, ProductCloseWithin31Checker, OwnerCloseWithin31Checker, ProductLevyOver31Checker, OwnerLevyOver31Checker, TransferCase115DayChecker) diff --git a/docs/자동차과태료_비교로직_정리.md b/docs/자동차과태료_비교로직_정리.md deleted file mode 100644 index 6cabdad..0000000 --- a/docs/자동차과태료_비교로직_정리.md +++ /dev/null @@ -1,1102 +0,0 @@ -# 자동차 과태료 비교 로직 명세서 - -## 개요 - -자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다. - -### 구현 위치 -- **서비스**: `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개, 이첩)