diff --git a/docs/자동차과태료_비교로직_정리.md b/docs/자동차과태료_비교로직_정리.md index abb4f06..f5d9fd0 100644 --- a/docs/자동차과태료_비교로직_정리.md +++ b/docs/자동차과태료_비교로직_정리.md @@ -3,13 +3,21 @@ ## 개요 자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다. -CarFfnlgTrgtServiceImpl.compareWithApi 메소드안에 해당 로직이 들어가야함 -기존에 사용하던 비교로직 서비스 : src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java -그러나 이젠 아예 비교로직이 틀어져서 compareWithApi 메소드 포함, ComparisonServiceImpl 서비스 자체를 처음부터 다시 개발해도 무방. -기존꺼 활용하기보다는 아예 재구축 검토 + +### 구현 위치 +- **서비스**: `ComparisonServiceImpl.java` +- **경로**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java` ### 기본 설정 -- 비교로직에 사용되는 API는 ExternalVehicleApiServiceImpl.getBasicInfo, getLedgerInfo 호출!! +- 비교로직에 사용되는 API는 `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출 +- 날짜 유틸리티: `DateUtil.parseDate()`, `DateUtil.isDateBetween()`, `DateUtil.formatDateString()` 사용 + +### 문서 이력 + +| 일자 | 변경 내용 | 비고 | +|------|----------|------| +| 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 | +| 2025-01-XX | 문서 업데이트 완료 | 신규 로직 반영 및 코드 예시 추가 | ### 처리 규칙 @@ -22,37 +30,102 @@ CarFfnlgTrgtServiceImpl.compareWithApi 메소드안에 해당 로직이 들어 ## 비교 로직 상세 -### 1. 상품용 검증 +### 1. 상품용 검증 (2025년 신규 로직) -**필요 API**: 자동차등록원부(갑) +**필요 API**: 자동차기본정보 (3회), 자동차등록원부(갑) (1회) #### API 호출 순서 -| 순서 | API | 입력 파라미터 | 출력 데이터 | -|------|-----|--------------|-------------| -| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | -| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | -| 3 | 자동차등록원본(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=조건에맞는CHG_YMD` | `소유자명` | 명의이전 시점 소유자 확인 | -#### 비교 조건 +#### 비교 조건 (순차 실행) ```java // 조건 1: 소유자명에 '상품용' 포함 여부 -api-1번호출.소유자명.contains("상품용") +if (!api1Response.소유자명.contains("상품용")) { + return false; // 미충족 +} + +// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 +LocalDate latestChgDate = null; +LedgerRecord targetRecord = null; -// 조건 2: 갑부 상세 목록에서 명의이전 이력 확인 for (LedgerRecord record : 갑부상세List) { - if (record.CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 - && record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자 - && record.CHANGE_JOB_SE_CODE == "11") { // 11 = 명의이전 코드 - return true; + // 2-1. 명의이전 코드 확인 + if (!record.CHG_TASK_SE_CD.equals("11")) { + continue; // 명의이전이 아니면 스킵 + } + + // 2-2. CHG_YMD <= 검사종료일자 + if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사종료일자) { + continue; // 검사종료일자 이후면 스킵 + } + + // 일단 주석처리 2-3. CHG_YMD <= 검사일 (검사일 이전일자만) + /* + if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사일) { + continue; // 검사일 이후면 스킵 + } + */ + + // 2-4. 가장 마지막 일자 찾기 + if (latestChgDate == null || record.CHG_YMD > latestChgDate) { + latestChgDate = record.CHG_YMD; + targetRecord = record; } } + +if (targetRecord == null) { + return false; // 조건에 맞는 레코드 없음 +} + +// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명 +if (!api4Response.소유자명.equals(api1Response.소유자명)) { + return false; // 소유자명 불일치 +} + +// 일단 주석처리 조건 4: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내 +/* +if (targetRecord.CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 + && targetRecord.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) { + return true; // 상품용 조건 충족 +} +*/ + +return false; // 미충족 ``` #### 결과 처리 -- **비고 컬럼**: `"[상품용] 갑부정보"` +- **업무 처리 상태 코드**: `02` (상품용) +- **비고 컬럼 형식**: +``` +상품용 + +■ 검사일 기준 소유자정보 + - 소유자명: {소유자명} + - 차대번호: {차대번호} + +■ 명의이전 시점 소유자정보 + - 소유자명: {소유자명} + - 조회일자: {CHG_YMD} + +■ 갑부 상세 (명의이전 이력) + - 변경일자: {CHG_YMD} + - 변경업무코드: 11 + - 변경업무명: 명의이전 + - 접수번호: {접수번호} + +■ 비교 기간 + - 유효기간만료일: {유효기간만료일} + - 검사종료일자: {검사종료일자} + - 판정: 명의이전일자가 기간 내 존재하고 검사일 소유자명과 일치 +``` --- @@ -166,75 +239,164 @@ private boolean checkTransferCondition_LegalDongMismatch( [차량번호 조회] │ ▼ -[1. 상품용 검증] ──(조건 충족)──> [비고 기록] ──> [다음 차량] +┌─────────────────────────────────────────────────────┐ +│ 1. 상품용 검증 (4단계 API 호출) │ +├─────────────────────────────────────────────────────┤ +│ Step 1: 자동차기본정보(차량번호, 검사일) │ +│ → 소유자명에 "상품용" 포함? │ +│ │ │ +│ ├─ (No) ──────────────────────────┐ │ +│ │ │ │ +│ └─ (Yes) │ │ +│ ▼ │ │ +│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │ +│ → 현재 소유자 정보 │ │ +│ │ │ │ +│ ▼ │ │ +│ Step 3: 자동차등록원부(갑) 조회 │ │ +│ → 갑부 상세 List │ │ +│ │ │ │ +│ ▼ │ │ +│ 검사일 이전 가장 마지막 명의이전(11) │ │ +│ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │ +│ │ │ │ +│ ├─ (없음) ─────────────────────┐ │ │ +│ │ │ │ │ +│ └─ (발견) │ │ │ +│ ▼ │ │ │ +│ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │ +│ → 명의이전 시점 소유자명 │ │ │ +│ │ │ │ │ +│ ▼ │ │ │ +│ 소유자명 일치? && 기간 내 존재? │ │ │ +│ │ │ │ │ +│ ├─ (No) ───────────────────┐ │ │ │ +│ │ │ │ │ │ +│ └─ (Yes) │ │ │ │ +│ ▼ │ │ │ │ +│ [상품용 처리 완료] │ │ │ │ +│ │ │ │ │ │ +│ └──> [다음 차량] <──────────┴───┴───┘ │ +└─────────────────────────────────────────────────────┘ │ │ (조건 미충족) ▼ -[2. DAYCNT 확인] - │ - ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일자 + 115일] - │ - └─ (<= 115) ──> [이첩-1: 부과기준일 = 검사일] - │ - ▼ - [법정동코드 비교] (공통) - │ - ├─ (불일치) ──> [비고 기록] ──> [다음 차량] - │ - └─ (일치) ──> [다음 차량] +┌─────────────────────────────────────────────────────┐ +│ 2. 이첩 검증 (이첩-1, 이첩-2) │ +├─────────────────────────────────────────────────────┤ +│ DAYCNT 확인 │ +│ │ │ +│ ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일+115일] │ +│ │ │ +│ └─ (<= 115) ─> [이첩-1: 부과기준일 = 검사일] │ +│ │ │ +│ ▼ │ +│ [자동차기본정보 API 호출] │ +│ │ │ +│ ▼ │ +│ [법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리] │ +│ │ │ +│ ├─ (불일치) ──> [이첩 처리 완료] ──> [다음 차량] │ +│ │ │ +│ └─ (일치) ────> [조건 미충족] ──> [다음 차량] │ +└─────────────────────────────────────────────────────┘ ``` --- ## 구현 시 주의사항 +### 공통 주의사항 1. **API 호출 순서 준수**: 각 검증 단계별로 필요한 API만 호출 2. **조건 우선순위**: 상품용 > 이첩 순서로 검증 3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동 4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록 -5. **법정동코드 길이 검증**: 최소 4자리 이상 필요 +5. **날짜 형식 지원**: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 (`DateUtil` 사용) + +### 상품용 검증 주의사항 +1. **4단계 API 호출 필수**: Step 1~4 모두 순차적으로 실행 +2. **가장 마지막 명의이전 레코드**: 검사일 이전일자 중 CHG_YMD가 가장 큰 값 선택 +3. **소유자명 정확한 일치**: Step1 소유자명 == Step4 소유자명 (완전 일치) +4. **기간 범위 확인**: CHG_YMD가 유효기간만료일 ~ 검사종료일자 사이에 존재해야 함 +5. **로그 상세 기록**: 각 단계별 결과를 상세히 로깅하여 디버깅 지원 + +### 이첩 검증 주의사항 +1. **법정동코드 길이 검증**: 최소 4자리 이상 필요 +2. **DAYCNT 기준 명확**: > 115 (초과), <= 115 (이하) 구분 +3. **사용자 조직코드 유효성**: null 체크 및 길이 확인 필수 ## 내가 text 로 정리한 내용 --- 1. 상품용 [자동차등록원부(갑) 필요] ------ 필요한 api 정보 -1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일] -> response.차대번호, response.소유자명 -2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] -> response.차량번호, response.성명, response.민원인주민번호, response.민원인법정동코드 +### 1. 상품용 (2025년 신규 로직) + +#### 필요한 API 정보 +1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일] + → response.차대번호, response.소유자명 + +2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] + → response.차량번호, response.성명, response.민원인주민번호, response.민원인법정동코드 + 3. 자동차등록원본(갑) api 호출 [2.response.차량번호, 2.response.성명, 2.response.민원인주민번호, 2.response.민원인법정동코드] - -- 비교로직에 사용될 api response 정보 ----------------------------------- -1. TB_CAR_FFNLG_TRGT.검사일 기준 api 호출 -2. tb_car_ffnlg_trgt.OWNR_NM like ‘%상품용%’ -3. (갑부 상세(LedgerRecord) List.CHG_YMD between TB_CAR_FFNLG_TRGT.유효기간만료일 and TB_CAR_FFNLG_TRGT.검사종료일자) and (갑부 상세 List.CHANGE_JOB_SE_CODE = '11' --명의이전 코드) -4. TB_CAR_FFNLG_TRGT 비고 : 조건에 걸린 - "[상품용] 갑부정보" - - --- 2. 이첩1, 2 병합로직 --- 부과기준일 구하는 부분만 다르고 나머지 법정도코드 비교로직은 동일, 대신 TB_CAR_FFNLG_TRGT 비고 컬럼에 각기 알맞는 value 값 넣어줘야함. -if(TB_CAR_FFNLG_TRGT.DAYCNT(textFile 일수) > 115){ - 부과기준일 = (TB_CAR_FFNLG_TRGT.검사종료일자 + 115일) - TB_CAR_FFNLG_TRGT 비고 : 조건에 걸린 - "전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]" - 이첩-2 -}else{ - 부과기준일 = (TB_CAR_FFNLG_TRGT.검사일자) - table 비고 : 조건에 걸린 - "서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" - 이첩-1 + → 갑부 상세 List + +4. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:조건에맞는CHG_YMD] + → response.소유자명 + +#### 비교 로직 순서 +1. api-1번호출.소유자명.contains("상품용") - 1차 필터링 +2. 갑부 상세 List에서 조건에 맞는 명의이전 레코드 찾기: + - record.CHG_TASK_SE_CD == "11" (명의이전 코드) + - record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자 + - 일단 주석처리 : record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사일 (검사일 이전일자 중) + - 위 조건 중 가장 마지막 일자 선택 +3. 4번 API로 명의이전 시점 소유자명 조회 +4. if (api-4번호출.소유자명 == api-1번호출.소유자명) { + return true; // 상품용 조건 충족 + /*일단 주석처리 + if (조건에맞는CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 + && 조건에맞는CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) { + return true; // 상품용 조건 충족 + } + */ + } +5. TB_CAR_FFNLG_TRGT 업무처리상태코드: 02 (상품용) +6. TB_CAR_FFNLG_TRGT 비고: 상품용 상세 정보 (위 참조) + + +### 2. 이첩1, 2 병합로직 + +#### 부과기준일 결정 +부과기준일 구하는 부분만 다르고 나머지 법정동코드 비교로직은 동일 + +```java +if (TB_CAR_FFNLG_TRGT.DAYCNT > 115) { + 부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자 + 115일 + // 이첩-2 + 비고 = "전라남도 순천시 / 김정대, 115일 도래지, [법정동코드 및 법정동명]" +} else { + 부과기준일 = TB_CAR_FFNLG_TRGT.검사일자 + // 이첩-1 + 비고 = "서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" } -아래는 이첩1,2 모두 공용 -자동차기본정보 (부과기준일, 차량번호)api call, +``` + +#### 법정동코드 비교 (이첩1, 2 공용) + +```java +// 자동차기본정보 API 호출 +자동차기본정보API.call(부과기준일, 차량번호); + // 법정동코드 유효성 검사 if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) { - log.debug("[이첩][조건1] 법정동코드 없음. 차량번호: {}", vhclno); return false; } // 사용자 정보 조회 SystemUserVO userInfo = userMapper.selectUser(userId); if (userInfo == null || userInfo.getOrgCd() == null) { - log.debug("[이첩][조건1] 사용자 정보 없음. 사용자ID: {}", userId); return false; } @@ -244,34 +406,26 @@ String userOrgCd = userInfo.getOrgCd(); String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd; if (legalDong4.equals(userOrg4)) { - log.debug("[이첩][조건1] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}", - vhclno, legalDong4, userOrg4); + // 법정동코드 일치 - 이첩 대상 아님 return false; } -log.info("[이첩][조건1] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}", - vhclno, legalDong4, userOrg4); +// 법정동코드 불일치 - 이첩 대상 return true; +``` +--- +## 구현 완료 상태 --------- 상품용 로직 완전 변경됨 - -| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | -| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | -| 3 | 자동차등록원본(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | -| 4 | 자동차기본정보 | `로직5번.차대번호`, `부과일자=로직5번.CHG_YMD` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | +### ✅ 상품용 로직 +- **구현 완료**: 신규 로직으로 구현 완료 +- **구현 위치**: `ComparisonServiceImpl.checkProductUse()` +- **주요 변경사항**: + - API 호출 3회 → 4회 (명의이전 시점 소유자명 확인 추가) + - 검사일 이전일자 중 가장 마지막 명의이전 레코드 찾기 + - 소유자명 일치 여부 확인 로직 추가 -// 로직 1: 소유자명에 '상품용' 포함 여부 - api-1번호출.소유자명.contains("상품용") -// 2. record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자 -// 3. && record.CHG_YMD 가장 마지막 일자, , 검사일 이전일자 중 가장 마지막 명의이전 record -// 4. && record.CHANGE_JOB_SE_CODE == "11" // 명의이전 -// 5. 2~4조건에 맞는 CHG_YMD, 차대번호 가져와 -// 6. 2~4조건에 맞는 CHG_YMD, 차대번호 정보를 가지고 -> 기본사항 조회 api 호출 -// 7. if(소유자명 == 첫번째 검사일기준 자동차기본정보 조회의 소유자명){ - if (r2~4조건에 맞는 CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 - && 2~4조건에 맞는 CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) - return true; - } -} \ No newline at end of file +### ✅ 이첩 로직 +- **구현 완료**: 이첩-1, 이첩-2 병합 로직 구현 완료 +- **구현 위치**: `ComparisonServiceImpl.checkTransfer()` \ No newline at end of file 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 6b05957..287d3ed 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 @@ -84,16 +84,23 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co // ======================================== /** - * 1. 상품용 검증 + * 1. 상품용 검증 (신규 로직) * - * API 호출 3단계: - * 1) 자동차기본정보(차량번호, 부과일자=검사일) -> 차대번호, 소유자명 - * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) -> 차량번호, 성명, 민원인주민번호, 민원인법정동코드 - * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) -> 갑부 상세 List + * API 호출 4단계: + * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명 + * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드 + * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List + * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD) → 해당 시점의 소유자명 * * 비교조건: - * - 소유자명에 "상품용" 포함 - * - 갑부 상세에서 명의이전(코드 11) 이력이 유효기간만료일~검사종료일자 사이에 있는지 + * - 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개의 조건은 주석처리 * * @param existingData 과태료 대상 데이터 * @return 02 (상품용) 또는 null (미적용) @@ -101,15 +108,15 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co private String checkProductUse(CarFfnlgTrgtVO existingData) { String vhclno = existingData.getVhclno(); String inspYmd = existingData.getInspYmd(); + String vldPrdExpryYmd = existingData.getVldPrdExpryYmd(); + String inspEndYmd = existingData.getInspEndYmd(); try { - // Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) + // ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ========== log.info("[상품용] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd); NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd); NewBasicResponse step1Response = apiService.getBasicInfo(step1Request); - - // API 응답에 CAR_FFNLG_TRGT_ID 업데이트 bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId()); if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) { @@ -119,27 +126,24 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co NewBasicResponse.Record step1Record = step1Response.getRecord().get(0); String vin = step1Record.getVin(); // 차대번호 - String ownerName = step1Record.getRprsOwnrNm(); // 대표소유자성명 + String step1OwnerName = step1Record.getRprsOwnrNm(); // 검사일 기준 소유자명 - log.info("[상품용] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, ownerName); + log.info("[상품용] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName); - // TODO : tb_car_ffnlg_trgt.OWNR_NM.contains("상품용") table, existingData 로 검사를 해야할지 고민... // 조건 1: 소유자명에 "상품용" 포함 여부 확인 - if (ownerName == null || !ownerName.contains("상품용")) { - log.debug("[상품용] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, ownerName); + if (step1OwnerName == null || !step1OwnerName.contains("상품용")) { + log.debug("[상품용] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); return null; } - log.info("[상품용] 소유자명에 '상품용' 포함 - 차량번호: {}, 소유자명: {}", vhclno, ownerName); + log.info("[상품용] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName); - // Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) + // ========== 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); - - // API 응답에 CAR_FFNLG_TRGT_ID 업데이트 bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtId()); if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) { @@ -150,20 +154,18 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co NewBasicResponse.Record step2Record = step2Response.getRecord().get(0); String currentVhclno = step2Record.getVhrno(); String currentOwnerName = step2Record.getRprsOwnrNm(); - String currentIdecno = step2Record.getRprsvOwnrIdecno(); // 민원인주민번호 - String currentLegalDongCode = step2Record.getUsgsrhldStdgCd(); // 민원인법정동코드 + String currentIdecno = step2Record.getRprsvOwnrIdecno(); + String currentLegalDongCode = step2Record.getUsgsrhldStdgCd(); log.info("[상품용] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); - // Step 3: 자동차등록원부(갑) 조회 + // ========== Step 3: 자동차등록원부(갑) 조회 ========== log.info("[상품용] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}", currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode); NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request); - - // API 응답에 CAR_FFNLG_TRGT_ID 업데이트 ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtId()); if (step3Response == null) { @@ -171,60 +173,126 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co return null; } - // 조건 2: 갑부 상세에서 명의이전 이력 확인 List ledgerRecords = step3Response.getRecord(); if (ledgerRecords == null || ledgerRecords.isEmpty()) { log.debug("[상품용] 갑부 상세 내역 없음 - 차량번호: {}", vhclno); return null; } - // 유효기간만료일 ~ 검사종료일자 사이에 명의이전(코드 11) 이력이 있는지 확인 - String vldPrdExpryYmd = existingData.getVldPrdExpryYmd(); - String inspEndYmd = existingData.getInspEndYmd(); + // ========== 갑부 상세에서 조건에 맞는 레코드 찾기 ========== + log.info("[상품용] 갑부 상세 레코드 검색 시작 - 검사일: {}, 검사종료일자: {}", inspYmd, inspEndYmd); - log.info("[상품용] 갑부 상세 비교 기간 - 유효기간만료일: {}, 검사종료일자: {}", vldPrdExpryYmd, inspEndYmd); + 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(); // 변경업무구분코드 - - log.debug("[상품용] 갑부 레코드 - 변경일자: {}, 변경업무구분코드: {}", chgYmd, chgTaskSeCd); - - // 명의이전 코드 11 확인 및 기간 확인 - if ("11".equals(chgTaskSeCd) && chgYmd != null) { - if (DateUtil.isDateBetween(chgYmd, vldPrdExpryYmd, inspEndYmd)) { - log.info("[상품용] 조건 충족! 차량번호: {}, 변경일자: {}, 변경업무: {}", vhclno, chgYmd, chgTaskSeCd); - - // 비고 생성 - 갑부 상세 정보 포함 - String rmrk = buildProductUseRemark( - step1Record, step2Record, record, - vldPrdExpryYmd, inspEndYmd - ); - - // DB 업데이트 - existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); - existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); - existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE); - existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER)); - existingData.setCarRegFrmbkChgTaskSeCd(record.getChgTaskSeCd()); - existingData.setCarRegFrmbkChgTaskSeNm(record.getChgTaskSeNm()); - existingData.setCarRegFrmbkChgYmd(record.getChgYmd()); - existingData.setCarRegFrmbkDtl(buildLedgerRecordDetail(record)); - 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_02_PRODUCT_USE; - } + 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); } } - log.debug("[상품용] 명의이전 이력이 기간 내 없음 - 차량번호: {}", vhclno); - return null; + if (targetRecord == null) { + log.debug("[상품용] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno); + return null; + } + + String targetChgYmd = targetRecord.getChgYmd(); + log.info("[상품용] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm()); + + // ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ========== + log.info("[상품용] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd); + + NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmd); + NewBasicResponse step4Response = apiService.getBasicInfo(step4Request); + bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtId()); + + if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) { + log.warn("[상품용] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd); + return null; + } + + NewBasicResponse.Record step4Record = step4Response.getRecord().get(0); + String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명 + + log.info("[상품용] Step 4 결과 - 소유자명: {}", step4OwnerName); + + // ========== 소유자명 비교 ========== + if (step4OwnerName == null || !step4OwnerName.equals(step1OwnerName)) { + log.debug("[상품용] 소유자명 불일치 - Step1 소유자명: {}, Step4 소유자명: {}", step1OwnerName, step4OwnerName); + return null; + } + + log.info("[상품용] 소유자명 일치 확인! - 소유자명: {}", step1OwnerName); + + // ========== 필요 없음. + // 어차피 상단에서 TB_CAR_FFNLG_TRGT.검사종료일자 작은것중의 마지막일자를 찾기때문에 + // 최종 조건: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내 ========== + /* + if (!DateUtil.isDateBetween(targetChgYmd, vldPrdExpryYmd, inspEndYmd)) { + log.debug("[상품용] CHG_YMD가 기간 범위 밖 - 변경일자: {}, 유효기간만료일: {}, 검사종료일자: {}", + targetChgYmd, vldPrdExpryYmd, inspEndYmd); + return null; + } + */ + + log.info("[상품용] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd); + + // ========== 비고 생성 ========== + String rmrk = buildProductUseRemark( + step1Record, step4Record, targetRecord, + vldPrdExpryYmd, inspEndYmd + ); + + // ========== DB 업데이트 ========== + existingData.setCarBassMatterInqireId(step1Response.getGeneratedId()); + existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId()); + existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE); + existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER)); + existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd()); + existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm()); + existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd()); + existingData.setCarRegFrmbkDtl(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_02_PRODUCT_USE; } catch (Exception e) { log.error("[상품용] 검증 중 오류 발생 - 차량번호: {}", vhclno, e); @@ -432,38 +500,32 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co /** * 상품용 비고 생성 * - * @param step1Record Step 1 API 응답 (검사일 기준) - * @param step2Record Step 2 API 응답 (오늘 기준) - * @param ledgerRecord 조건에 걸린 갑부 레코드 + * @param step1Record Step 1 API 응답 (검사일 기준 소유자명) + * @param step4Record Step 4 API 응답 (명의이전 시점 소유자명) + * @param ledgerRecord 조건에 맞는 갑부 레코드 * @param vldPrdExpryYmd 유효기간만료일 * @param inspEndYmd 검사종료일자 * @return 비고 문자열 */ private String buildProductUseRemark( NewBasicResponse.Record step1Record, - NewBasicResponse.Record step2Record, + NewBasicResponse.Record step4Record, NewLedgerResponse.Record ledgerRecord, String vldPrdExpryYmd, String inspEndYmd) { StringBuilder sb = new StringBuilder(); - sb.append("상품용\n"); + sb.append("상품용 [검사종료일 이전 명의이전]\n"); - // 1. 검사일 기준 정보 - /* - sb.append("■ 검사일 기준 차량정보\n"); - sb.append(" - 차대번호: ").append(nvl(step1Record.getVin())).append("\n"); + // 1. 검사일 기준 소유자 정보 + sb.append("\n■ 검사일 기준 소유자정보\n"); sb.append(" - 소유자명: ").append(nvl(step1Record.getRprsOwnrNm())).append("\n"); - */ + sb.append(" - 차대번호: ").append(nvl(step1Record.getVin())).append("\n"); - // 2. 현재(오늘) 기준 정보 - /* - sb.append("\n■ 현재 차량정보\n"); - sb.append(" - 차량번호: ").append(nvl(step2Record.getVhrno())).append("\n"); - sb.append(" - 소유자명: ").append(nvl(step2Record.getRprsOwnrNm())).append("\n"); - sb.append(" - 주민번호: ").append(maskIdecno(step2Record.getRprsvOwnrIdecno())).append("\n"); - sb.append(" - 법정동코드: ").append(nvl(step2Record.getUsgsrhldStdgCd())).append("\n"); - */ + // 2. 명의이전 시점 소유자 정보 + sb.append("\n■ 명의이전 시점 소유자정보\n"); + sb.append(" - 소유자명: ").append(nvl(step4Record.getRprsOwnrNm())).append("\n"); + sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n"); // 3. 갑부 상세 정보 (명의이전 이력) sb.append("\n■ 갑부 상세 (명의이전 이력)\n"); @@ -471,18 +533,14 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co sb.append(" - 변경업무코드: ").append(nvl(ledgerRecord.getChgTaskSeCd())).append("\n"); sb.append(" - 변경업무명: ").append(nvl(ledgerRecord.getChgTaskSeNm())).append("\n"); sb.append(" - 접수번호: ").append(nvl(ledgerRecord.getAplyRcptNo())).append("\n"); - /* - sb.append(" - 주번호: ").append(nvl(ledgerRecord.getMainNo())).append("\n"); - sb.append(" - 부번호: ").append(nvl(ledgerRecord.getSno())).append("\n"); - sb.append(" - 사항란: ").append(nvl(ledgerRecord.getSpcablMttr())).append("\n"); - sb.append(" - 세대주명: ").append(nvl(ledgerRecord.getHshldrNm())).append("\n"); - */ + /* // 4. 비교 기간 sb.append("\n■ 비교 기간\n"); sb.append(" - 유효기간만료일: ").append(DateUtil.formatDateString(vldPrdExpryYmd)).append("\n"); sb.append(" - 검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n"); - sb.append(" - 판정: 명의이전일자가 기간 내 존재"); + sb.append(" - 판정: 명의이전일자가 기간 내 존재하고 검사일 소유자명과 일치"); + */ return sb.toString(); } diff --git a/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp b/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp index a2c3b4c..de54693 100644 --- a/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp +++ b/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp @@ -1,6 +1,8 @@ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="dateUtil" uri="http://egovframework.go.kr/functions/date-util" %>
@@ -19,8 +21,8 @@