# 자동차 과태료 비교 로직 명세서 (지연) ## 개요 자동차 과태료 부과 대상(지연)을 검증하기 위한 비교 로직 정의서입니다. ### 구현 위치 - **Checker 클래스**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/delay_checker/` - `ProductUseChecker.java` - 1. 상품용(명의이전) - `ProductUseChnageChecker.java` - 1-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. 상품용(명의이전) → 1-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()); ``` --- ### 1-1. 상품용-변경등록 검증 (`ProductUseChnageChecker`) **처리상태코드**: `02` (상품용) **클래스**: `ProductUseChnageChecker.java` #### API 호출 순서 | 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | |------|-----|--------------|-------------|------| | 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호(vin)`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 | | 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 | | 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 변경등록 이력 조회 | | 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 변경등록 시점 소유자 확인 | #### 비교 조건 ``` 1. Step1 소유자명.contains("상품용") ← 상품용 아니면 return null 2. 갑부에서 변경등록(21) 레코드 찾기 - CHG_TASK_SE_CD == "21" - CHG_YMD <= 검사종료일자 - spcablMttr.contains("성명") ← 성명 변경 포함 필수 - 가장 마지막 일자 선택 ← 없으면 return null 3. Step4 대표소유자회원번호 == Step1 대표소유자회원번호 ← 불일치면 return null → 모든 조건 충족: 상품용(02) 처리 ``` #### DB 업데이트 필드 ```java existingData.setTaskPrcsSttsCd("02"); // 상품용 existingData.setCarBscMttrInqFlnm(step4OwnerName); existingData.setCarRegFrmbkChgTaskSeCd("21"); 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` | `"{시군구명}, 검사일사용본거지, [사용자 조직코드 앞 4자리: {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("상품용") │ │ + 명의이전(11) 레코드 존재 │ │ → 조건 충족: 상품용(02) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 1-1. 상품용-변경등록 (ProductUseChnageChecker)│ │ 조건: 검사일 소유자명.contains("상품용") │ │ + 변경등록(21) + 성명변경 포함 │ │ → 조건 충족: 상품용(02) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 2. 내사종결-상품용 (ProductCloseWithin31Checker) │ │ 조건: 31일 이내 + 명의이전 전 상품용 │ │ → 조건 충족: 내사종결(04) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 3. 내사종결-명의이전 (OwnerCloseWithin31Checker) │ │ 조건: 31일 이내 + 동일 소유자 │ │ → 조건 충족: 내사종결(04) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 4. 날짜수정-상품용 (ProductLevyOver31Checker) │ │ 조건: 31일 초과 + 명의이전 전 상품용 │ │ → 조건 충족: 날짜수정후부과(05) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 5. 날짜수정-명의이전 (OwnerLevyOver31Checker) │ │ 조건: 31일 초과 + 동일 소유자 │ │ → 조건 충족: 날짜수정후부과(05) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ ┌──────────────────────────────────────────────┐ │ 6. 이첩 (TransferCase115DayChecker) │ │ 조건: 법정동코드 앞4자리 != 조직코드 앞4자리│ │ → 조건 충족: 이첩(03) │ └──────────────────────────────────────────────┘ │ (조건 미충족) ▼ [다음 차량] ``` --- ## 요약 정리 ### 처리상태코드 매핑 | 코드 | 상태명 | 처리 로직 | 클래스 | |------|--------|----------|--------| | 02 | 상품용 | 검사일 소유자가 상품용 + 명의이전(11) | `ProductUseChecker` | | 02 | 상품용 | 검사일 소유자가 상품용 + 변경등록(21) + 성명변경 | `ProductUseChnageChecker` | | 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" = 명의이전 "21" = 변경등록 ``` --- **문서 작성 완료일**: 2025-12-08 **실제 소스 코드 기준**: delay_checker 폴더 내 Checker 클래스들 **분석 대상 클래스**: 7개 (ProductUseChecker, ProductUseChnageChecker, ProductCloseWithin31Checker, OwnerCloseWithin31Checker, ProductLevyOver31Checker, OwnerLevyOver31Checker, TransferCase115DayChecker)