You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
24 KiB
24 KiB
자동차 과태료 비교 로직 명세서
개요
자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다.
구현 위치
- 서비스:
ComparisonServiceImpl.java - 경로:
src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java
기본 설정
- 비교로직에 사용되는 API는
ExternalVehicleApiServiceImpl.getBasicInfo,getLedgerInfo호출 - 날짜 유틸리티:
DateUtil.parseDate(),DateUtil.isDateBetween(),DateUtil.formatDateString()사용
문서 이력
| 일자 | 변경 내용 | 비고 |
|---|---|---|
| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 30일 이내 조건 추가 |
| 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 |
처리 규칙
중요: 순서가 중요함!
- 상품용 검증 (Case1) → 2. 내사종결 상품용 검증 (Case2) → 3. 이첩 검증
- 조건에 걸리는 순간 다음 차량번호 비교로 진행
- 각 비교 로직별로 개별 API 호출 수행
비교 로직 상세
1. 상품용 검증 Case1 - 검사일 소유자가 상품용
처리상태코드: 02 (상품용)
메서드: checkProductUseCase1() (ComparisonServiceImpl.java:113~306)
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=조건에맞는CHG_YMD |
소유자명 |
명의이전 시점 소유자 확인 |
비교 조건 (순차 실행)
// 조건 1: 소유자명에 '상품용' 포함 여부 (Line 138-142)
if (!step1Record.소유자명.contains("상품용")) {
return null; // 미충족
}
// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 (Line 187-234)
LocalDate latestChgDate = null;
NewLedgerResponse.Record targetRecord = null;
for (NewLedgerResponse.Record record : ledgerRecords) {
// 2-1. 명의이전 코드 확인 (Line 199)
if (!"11".equals(record.CHG_TASK_SE_CD)) {
continue; // 명의이전이 아니면 스킵
}
// 2-2. CHG_YMD <= 검사종료일자 (Line 208-213)
if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사종료일자) {
continue; // 검사종료일자 이후면 스킵
}
// 주석처리됨: CHG_YMD <= 검사일 조건 (Line 215-221)
// → 검사일 이후 데이터도 포함 (검사종료일자까지만 제한)
// 2-3. 가장 마지막 일자 찾기 (Line 224-228)
if (latestChgDate == null || record.CHG_YMD > latestChgDate) {
latestChgDate = record.CHG_YMD;
targetRecord = record;
}
}
if (targetRecord == null) {
return null; // 조건에 맞는 레코드 없음
}
// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명 (Line 257-260)
if (!step4Record.소유자명.equals(step1Record.소유자명)) {
return null; // 소유자명 불일치
}
// 주석처리됨: CHG_YMD 기간 범위 확인 (Line 264-273)
// → 기간 범위 체크하지 않음
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE; // 상품용 조건 충족
주요 변경사항
-
주석처리된 조건:
→ 제거됨CHG_YMD <= 검사일(검사일 이전일자 중)→ 제거됨CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내
-
적용되는 조건:
CHG_YMD <= 검사종료일자(검사종료일자 이전)- 명의이전(코드 11) 레코드 중 가장 마지막 일자
- 소유자명 일치 확인
결과 처리
- 업무 처리 상태 코드:
02(상품용) - 비고 컬럼 형식: (Line 732-761, 대부분 주석처리됨)
상품용 - 상품용검사
2. 내사종결 상품용 검증 Case2 - 명의이전 후 상품용
처리상태코드: 04 (내사종결)
메서드: checkCloseProductUse() (ComparisonServiceImpl.java:329~515)
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=CHG_YMD-1일 |
소유자명 |
명의이전 직전 소유자 확인 |
비교 조건 (순차 실행)
// 조건 1: 소유자명에 '상품용' 미포함 (Line 354-358)
if (step1Record.소유자명 == null || step1Record.소유자명.contains("상품용")) {
return null; // 미충족 (검사일 소유자가 상품용이면 Case1에서 처리됨)
}
// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 (Line 403-437)
LocalDate latestChgDate = null;
NewLedgerResponse.Record targetRecord = null;
for (NewLedgerResponse.Record record : ledgerRecords) {
// 2-1. 명의이전 코드 확인 (Line 415)
if (!"11".equals(record.CHG_TASK_SE_CD)) {
continue; // 명의이전이 아니면 스킵
}
// 2-2. CHG_YMD <= 검사일자 (Line 424-429)
if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사일자) {
continue; // 검사일자 이후면 스킵
}
// 2-3. 가장 마지막 일자 찾기 (Line 432-436)
if (latestChgDate == null || record.CHG_YMD > latestChgDate) {
latestChgDate = record.CHG_YMD;
targetRecord = record;
}
}
if (targetRecord == null) {
return null; // 조건에 맞는 레코드 없음
}
// 조건 3: 명의이전일자가 검사일의 30일 이내인지 확인 (Line 444-450)
long daysBetween = ChronoUnit.DAYS.between(latestChgDate, 검사일);
if (daysBetween < 0 || daysBetween > 30) {
return null; // 30일 초과
}
// 조건 4: 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 '상품용' 포함 (Line 476-482)
// Step 4 API: 부과일자 = CHG_YMD - 1일 (Line 457-464)
if (step4Record.소유자명 == null || !step4Record.소유자명.contains("상품용")) {
return null; // 명의이전 직전 소유자가 상품용이 아님
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; // 내사종결 조건 충족
로직 설명
이 로직은 검사일 당시에는 상품용이 아니었지만, 명의이전 이전(30일 이내)에는 상품용이었던 경우를 처리합니다:
- 검사일 소유자가 상품용이 아님
- 검사일 이전 30일 이내에 명의이전(코드 11) 발생
- 명의이전 직전 소유자가 상품용
결과 처리
- 업무 처리 상태 코드:
04(내사종결) - 비고 컬럼 형식: (Line 784-818)
명의이전(25.9.3.) 이전소유자 상품용
22루2283
- 검사기간: 2024-08-01 - 2024-08-31
- 검사일: 2024-08-15
- 명의이전: 2024-09-03
- 상품용: 2024-09-03
3. 이첩 검증 (이첩-1, 이첩-2 병합 로직)
처리상태코드: 03 (이첩)
메서드: checkTransferCase115Day() (ComparisonServiceImpl.java:531~648)
필요 API: 자동차기본정보
부과기준일 결정
int dayCnt = TB_CAR_FFNLG_TRGT.DAYCNT; // textFile 일수
if (dayCnt > 115) {
// 이첩-2
부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자.plusDays(115);
} else {
// 이첩-1
부과기준일 = TB_CAR_FFNLG_TRGT.검사일자;
}
API 호출
// 부과기준일 기준으로 자동차기본정보 API 호출
BasicResponse response = 자동차기본정보API.call(부과기준일, 차량번호);
법정동코드 비교 로직 (공통)
/**
* 이첩 조건: 법정동코드 불일치 검증
* 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
*/
private boolean checkTransferCondition_LegalDongMismatch(
BasicResponse.Record basicInfo,
String userId,
String vhclno
) {
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
// 1. 법정동코드 유효성 검사
if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) {
log.debug("[이첩] 법정동코드 없음. 차량번호: {}", vhclno);
return false;
}
// 2. 사용자 정보 조회
SystemUserVO userInfo = userMapper.selectUser(userId);
if (userInfo == null || userInfo.getOrgCd() == null) {
log.debug("[이첩] 사용자 정보 없음. 사용자ID: {}", userId);
return false;
}
// 3. 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
String userOrgCd = userInfo.getOrgCd();
String userOrg4 = userOrgCd.length() >= 4
? userOrgCd.substring(0, 4)
: userOrgCd;
// 4. 일치 여부 판단
if (legalDong4.equals(userOrg4)) {
log.debug("[이첩] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}",
vhclno, legalDong4, userOrg4);
return false; // 일치하면 이첩 대상 아님
}
log.info("[이첩] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
vhclno, legalDong4, userOrg4);
return true; // 불일치하면 이첩 대상
}
결과 처리
| 구분 | 조건 | 비고 컬럼 형식 |
|---|---|---|
| 이첩-1 | DAYCNT <= 115 |
"서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" |
| 이첩-2 | DAYCNT > 115 |
"전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]" |
데이터 모델
TB_CAR_FFNLG_TRGT (과태료 대상 테이블)
| 컬럼명 | 설명 | 용도 |
|---|---|---|
| 검사일 | 검사 기준일 | API 호출 파라미터 |
| 검사종료일자 | 검사 종료 일자 | 115일 계산 기준 |
| 유효기간만료일 | 유효기간 만료일 | 상품용 갑부 비교 시작일 |
| DAYCNT | textFile 일수 | 이첩-1/2 분기 조건 |
| 비고 | 검증 결과 메시지 | 결과 저장 |
코드 정의
| 코드 | 코드값 | 설명 |
|---|---|---|
| CHANGE_JOB_SE_CODE | 11 | 명의이전 |
처리 흐름도
시작
│
▼
[차량번호 조회]
│
▼
┌────────────────────────────────────────────────────────────┐
│ 1. 상품용 Case1 - 검사일 소유자가 상품용 │
├────────────────────────────────────────────────────────────┤
│ Step 1: 자동차기본정보(차량번호, 검사일) │
│ → 소유자명에 "상품용" 포함? │
│ │ │
│ ├─ (No) ──────────────────────────────────┐ │
│ │ │ │
│ └─ (Yes) │ │
│ ▼ │ │
│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │
│ → 현재 소유자 정보 │ │
│ ▼ │ │
│ Step 3: 자동차등록원부(갑) 조회 │ │
│ → 갑부 상세 List │ │
│ ▼ │ │
│ 검사종료일자 이전 가장 마지막 명의이전(11) │ │
│ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │
│ │ │ │
│ ├─ (없음) ─────────────────────────────┐ │ │
│ │ │ │ │
│ └─ (발견) │ │ │
│ ▼ │ │ │
│ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │
│ → 명의이전 시점 소유자명 │ │ │
│ │ │ │ │
│ ▼ │ │ │
│ 소유자명 일치? │ │ │
│ │ │ │ │
│ ├─ (No) ─────────────────────────┐ │ │ │
│ │ │ │ │ │
│ └─ (Yes) │ │ │ │
│ ▼ │ │ │ │
│ [상품용(02) 처리 완료] │ │ │ │
│ │ │ │ │ │
│ └──> [다음 차량] <────────────────┴─────┴───┘ │
└────────────────────────────────────────────────────────────┘
│
│ (조건 미충족)
▼
┌────────────────────────────────────────────────────────────┐
│ 2. 내사종결 Case2 - 명의이전 후 상품용 │
├────────────────────────────────────────────────────────────┤
│ Step 1: 자동차기본정보(차량번호, 검사일) │
│ → 소유자명에 "상품용" 미포함? │
│ │ │
│ ├─ (No) ──────────────────────────────────┐ │
│ │ │ │
│ └─ (Yes) │ │
│ ▼ │ │
│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │
│ → 현재 소유자 정보 │ │
│ ▼ │ │
│ Step 3: 자동차등록원부(갑) 조회 │ │
│ → 갑부 상세 List │ │
│ ▼ │ │
│ 검사일 이전 가장 마지막 명의이전(11) │ │
│ 레코드 찾기 (CHG_YMD <= 검사일) │ │
│ │ │ │
│ ├─ (없음) ─────────────────────────────┐ │ │
│ │ │ │ │
│ └─ (발견) │ │ │
│ ▼ │ │ │
│ 명의이전일자가 검사일의 30일 이내? │ │ │
│ │ │ │ │
│ ├─ (No) ─────────────────────────┐ │ │ │
│ │ │ │ │ │
│ └─ (Yes) │ │ │ │
│ ▼ │ │ │ │
│ Step 4: 자동차기본정보(차대번호, CHG_YMD-1일) │ │ │ │
│ → 명의이전 직전 소유자명 │ │ │ │
│ │ │ │ │ │
│ ▼ │ │ │ │
│ 소유자명에 "상품용" 포함? │ │ │ │
│ │ │ │ │ │
│ ├─ (No) ───────────────────┐ │ │ │ │
│ │ │ │ │ │ │
│ └─ (Yes) │ │ │ │ │
│ ▼ │ │ │ │ │
│ [내사종결(04) 처리 완료] │ │ │ │ │
│ │ │ │ │ │ │
│ └──> [다음 차량] <──────────┴─────┴─────┴───┘ │
└────────────────────────────────────────────────────────────┘
│
│ (조건 미충족)
▼
┌────────────────────────────────────────────────────────────┐
│ 3. 이첩 검증 (이첩-1, 이첩-2) │
├────────────────────────────────────────────────────────────┤
│ DAYCNT 확인 │
│ │ │
│ ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일+115일] │
│ │ │
│ └─ (<= 115) ─> [이첩-1: 부과기준일 = 검사일] │
│ │ │
│ ▼ │
│ [자동차기본정보 API 호출] │
│ │ │
│ ▼ │
│ [법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리] │
│ │ │
│ ├─ (불일치) ──> [이첩(03) 처리 완료] ──> [다음 차량] │
│ │ │
│ └─ (일치) ────> [조건 미충족] ──> [다음 차량] │
└────────────────────────────────────────────────────────────┘
구현 시 주의사항
공통 주의사항
- API 호출 순서 준수: 각 검증 단계별로 필요한 API만 호출
- 조건 우선순위: 상품용 Case1 > 내사종결 Case2 > 이첩 순서로 검증
- 조기 종료: 조건 충족 시 즉시 다음 차량으로 이동
- 비고 컬럼: 각 조건별 정해진 형식으로 기록
- 날짜 형식 지원: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 (
DateUtil사용)
상품용 Case1 주의사항
- 4단계 API 호출 필수: Step 1~4 모두 순차적으로 실행
- 가장 마지막 명의이전 레코드: 검사종료일자 이전일자 중 CHG_YMD가 가장 큰 값 선택
- 소유자명 정확한 일치: Step1 소유자명 == Step4 소유자명 (완전 일치)
- 주석처리된 조건:
검사일 이전일자 제한→ 검사종료일자 이전일자만 제한기간 범위 확인→ 제거됨
- 로그 상세 기록: 각 단계별 결과를 상세히 로깅하여 디버깅 지원
내사종결 Case2 주의사항
- 검사일 소유자 확인: 검사일 소유자가 상품용이 아니어야 함 (Case1과 구분)
- 30일 이내 조건: 명의이전일자가 검사일의 30일 이내여야 함
- CHG_YMD-1일 조회: Step 4에서 명의이전 직전 시점(CHG_YMD - 1일) 조회
- 검사일 기준: 갑부 레코드는 검사일 이전일자 중 가장 마지막 일자 선택
- 비고 형식: 명의이전 날짜, 차량번호, 검사기간 등 상세 정보 포함
이첩 검증 주의사항
- 법정동코드 길이 검증: 최소 4자리 이상 필요
- DAYCNT 기준 명확: > 115 (초과), <= 115 (이하) 구분
- 사용자 조직코드 유효성: null 체크 및 길이 확인 필수
요약 정리
비교 로직 실행 순서 (executeComparison)
// ComparisonServiceImpl.java Line 50-85
public String executeComparison(CarFfnlgTrgtVO existingData) {
// 1. 상품용 Case1 - 검사일 소유자가 상품용
String result1 = checkProductUseCase1(existingData);
if (result1 != null) return result1; // "02"
// 2. 내사종결 Case2 - 명의이전 후 상품용
String result2 = checkCloseProductUse(existingData);
if (result2 != null) return result2; // "04"
// 3. 이첩 검증
String result3 = checkTransferCase115Day(existingData);
if (result3 != null) return result3; // "03"
// 모든 조건 미충족
return null;
}
처리상태코드 매핑
| 코드 | 상태명 | 처리 로직 | 메서드 |
|---|---|---|---|
| 02 | 상품용 | 검사일 소유자가 상품용 | checkProductUseCase1() |
| 04 | 내사종결 | 명의이전(30일 이내) 후 상품용 | checkCloseProductUse() |
| 03 | 이첩 | 법정동코드 불일치 | checkTransferCase115Day() |
주요 차이점 정리
| 구분 | Case1 (상품용) | Case2 (내사종결) |
|---|---|---|
| 검사일 소유자 | 상품용 포함 | 상품용 미포함 |
| 갑부 레코드 기준 | CHG_YMD <= 검사종료일자 | CHG_YMD <= 검사일 |
| 추가 조건 | - | 명의이전일자가 검사일의 30일 이내 |
| Step 4 조회일 | CHG_YMD | CHG_YMD - 1일 |
| Step 4 확인사항 | 소유자명 일치 | 소유자명에 "상품용" 포함 |
| 처리상태코드 | 02 | 04 |
구현 완료 상태
✅ 상품용 Case1 로직
- 구현 완료: 신규 로직으로 구현 완료
- 구현 위치:
ComparisonServiceImpl.checkProductUseCase1()(Line 113-306) - 주요 변경사항:
- 검사일 이전일자 조건 주석처리 → 검사종료일자 이전일자만 제한
- 기간 범위 확인 조건 주석처리 → 제거됨
- 비고 내용 간소화
✅ 내사종결 Case2 로직 (신규 추가)
- 구현 완료: 신규 로직 추가 완료
- 구현 위치:
ComparisonServiceImpl.checkCloseProductUse()(Line 329-515) - 주요 특징:
- 명의이전(30일 이내) 후 상품용인 경우 처리
- CHG_YMD - 1일 기준 조회
- 상세한 비고 정보 생성
✅ 이첩 로직
- 구현 완료: 이첩-1, 이첩-2 병합 로직 구현 완료
- 구현 위치:
ComparisonServiceImpl.checkTransferCase115Day()(Line 531-648)