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.
21 KiB
21 KiB
자동차 과태료 비교 로직 명세서
개요
자동차 과태료 부과 대상을 검증하기 위한 비교 로직 정의서입니다.
구현 위치
- 서비스:
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 | 명의이전 케이스 추가 (내사종결, 날짜 수정 후 부과) | 상품용과 순수 명의이전 분리 |
| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 31일 이내 조건 추가 |
| 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 |
처리 규칙
중요: 순서가 중요함!
- 상품용 → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 날짜수정(상품용) → 5. 날짜수정(명의이전) → 6. 이첩
- 조건에 걸리는 순간 다음 차량번호 비교로 진행
- 각 비교 로직별로 개별 API 호출 수행
비교 로직 상세
1. 상품용 검증 - 검사일 소유자가 상품용
처리상태코드: 02 (상품용)
메서드: checkProductUse()
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=조건에맞는CHG_YMD |
소유자명 |
명의이전 시점 소유자 확인 |
비교 조건
// 조건 1: 소유자명에 '상품용' 포함 여부
if (!step1Record.소유자명.contains("상품용")) {
return null; // 미충족
}
// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기
// - CHG_TASK_SE_CD == "11" (명의이전)
// - CHG_YMD <= 검사종료일자
// - 가장 마지막 일자
// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명
if (!step4Record.소유자명.equals(step1Record.소유자명)) {
return null; // 소유자명 불일치
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE; // 상품용 조건 충족
결과 처리
- 업무 처리 상태 코드:
02(상품용) - 비고 컬럼 형식:
상품용 - 상품용검사
2. 내사종결 검증 - 명의이전 전 소유자 상품용 (31일 이내)
처리상태코드: 04 (내사종결)
메서드: checkInvestigationClosedByProductUseWithin31Days()
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=CHG_YMD-1일 |
소유자명 |
명의이전 직전 소유자 확인 |
비교 조건
// 조건 1: 검사일 소유자명에 '상품용' 미포함 (상품용은 1번에서 처리됨)
if (step1Record.소유자명 == null || step1Record.소유자명.contains("상품용")) {
return null;
}
// 조건 2: 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기
// - CHG_TASK_SE_CD == "11" (명의이전)
// - CHG_YMD <= 검사일자
// - 가장 마지막 일자
// 조건 3: 명의이전일자가 검사일의 31일 이내
long daysBetween = ChronoUnit.DAYS.between(latestChgDate, 검사일);
if (daysBetween < 0 || daysBetween > 31) {
return null; // 31일 초과
}
// 조건 4: 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 '상품용' 포함
if (step4Record.소유자명 == null || !step4Record.소유자명.contains("상품용")) {
return null; // 명의이전 직전 소유자가 상품용이 아님
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;
로직 설명
검사일 당시에는 상품용이 아니었지만, 명의이전 이전(31일 이내)에는 상품용이었던 경우
- 검사일 소유자가 상품용 아님
- 검사일 이전 31일 이내에 명의이전(코드 11) 발생
- 명의이전 직전 소유자가 상품용
결과 처리
- 업무 처리 상태 코드:
04(내사종결) - 비고 컬럼 형식:
명의이전(25.9.3.) 이전소유자 상품용
22루2283
- 검사기간: 2024-08-01 - 2024-08-31
- 검사일: 2024-08-15
- 명의이전: 2024-09-03
- 상품용: 2024-09-03
일수차이: 19일
3. 내사종결 검증 - 순수 명의이전 (31일 이내)
처리상태코드: 04 (내사종결)
메서드: checkInvestigationClosedByOwnerChangeWithin31Days()
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=CHG_YMD-1일 |
소유자명 |
명의이전 직전 소유자 확인 |
비교 조건
// 조건 1: 검사일 소유자명에 '상품용' 미포함
if (step1Record.소유자명 != null && step1Record.소유자명.contains("상품용")) {
return null; // 상품용이면 1번에서 처리됨
}
// 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기
// - CHG_TASK_SE_CD == "11" (명의이전)
// - 유효기간만료일 <= CHG_YMD <= 검사종료일자
// 조건 3: 명의이전일자가 검사일의 31일 이내
long daysBetween = ChronoUnit.DAYS.between(chgDate, 검사일);
if (daysBetween < 0 || daysBetween > 31) {
return null;
}
// 조건 4: 명의이전 직전 소유자가 상품용 아님 (상품용이면 2번에서 처리됨)
if (step4Record.소유자명 == null || step4Record.소유자명.contains("상품용")) {
return null; // 상품용이면 이미 2번에서 처리됨
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;
로직 설명
순수 명의이전 케이스 (상품용과 무관)
- 검사일 소유자가 상품용 아님
- 검사기간 내 명의이전(코드 11) 발생
- 명의이전일자가 검사일의 31일 이내
- 명의이전 직전 소유자도 상품용 아님 (상품용이면 2번 로직에서 처리됨)
결과 처리
- 업무 처리 상태 코드:
04(내사종결) - 비고 컬럼 형식:
명의이전(25.9.3.)
22루2283
- 검사기간: 2024-08-01 - 2024-08-31
- 검사일: 2024-08-15
- 명의이전: 2024-09-03
- 상품용: 2024-09-03
일수차이: 19일
4. 날짜 수정 후 부과 검증 - 명의이전 전 소유자 상품용 (31일 초과)
처리상태코드: 05 (날짜 수정 후 부과)
메서드: checkDateModifiedLevyByProductUseOver31Days()
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=CHG_YMD-1일 |
소유자명 |
명의이전 직전 소유자 확인 |
비교 조건
// 조건 1: 검사일 소유자명에 '상품용' 미포함
if (step1Record.소유자명 == null || step1Record.소유자명.contains("상품용")) {
return null;
}
// 조건 2: 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기
// - CHG_TASK_SE_CD == "11" (명의이전)
// - CHG_YMD <= 검사일자
// - 가장 마지막 일자
// 조건 3: 명의이전일자가 검사일의 31일 초과
long daysBetween = ChronoUnit.DAYS.between(latestChgDate, 검사일);
if (daysBetween <= 31) {
return null; // 31일 이내면 2번에서 처리됨
}
// 조건 4: 명의이전 직전 소유자가 상품용
if (step4Record.소유자명 == null || !step4Record.소유자명.contains("상품용")) {
return null;
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;
로직 설명
명의이전 전 소유자가 상품용이지만 31일을 초과한 경우
- 검사일 소유자가 상품용 아님
- 검사일 이전에 명의이전(코드 11) 발생
- 명의이전일자가 검사일의 31일 초과
- 명의이전 직전 소유자가 상품용
결과 처리
- 업무 처리 상태 코드:
05(날짜 수정 후 부과) - 비고 컬럼 형식: 2번과 동일
5. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과)
처리상태코드: 05 (날짜 수정 후 부과)
메서드: checkDateModifiedLevyByOwnerChangeOver31Days()
API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|---|---|---|---|---|
| 1 | 자동차기본정보 | 차량번호, 부과일자=검사일 |
차대번호, 소유자명 |
검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | 1.차대번호, 부과일자=오늘일자 |
차량번호, 성명, 민원인주민번호, 민원인법정동코드 |
현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 |
갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | 1.차대번호, 부과일자=CHG_YMD-1일 |
소유자명 |
명의이전 직전 소유자 확인 |
비교 조건
// 조건 1: 검사일 소유자명에 '상품용' 미포함
if (step1Record.소유자명 != null && step1Record.소유자명.contains("상품용")) {
return null;
}
// 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기
// - CHG_TASK_SE_CD == "11" (명의이전)
// - 유효기간만료일 <= CHG_YMD <= 검사종료일자
// 조건 3: 명의이전일자가 검사일의 31일 초과
long daysBetween = ChronoUnit.DAYS.between(chgDate, 검사일);
if (daysBetween <= 31) {
return null; // 31일 이내면 3번에서 처리됨
}
// 조건 4: 명의이전 직전 소유자가 상품용 아님
if (step4Record.소유자명 == null || step4Record.소유자명.contains("상품용")) {
return null; // 상품용이면 4번에서 처리됨
}
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;
로직 설명
순수 명의이전 케이스 중 31일을 초과한 경우
- 검사일 소유자가 상품용 아님
- 검사기간 내 명의이전(코드 11) 발생
- 명의이전일자가 검사일의 31일 초과
- 명의이전 직전 소유자도 상품용 아님 (상품용이면 4번 로직에서 처리됨)
결과 처리
- 업무 처리 상태 코드:
05(날짜 수정 후 부과) - 비고 컬럼 형식: 3번과 동일
6. 이첩 검증 (이첩-1, 이첩-2)
처리상태코드: 03 (이첩)
메서드: checkTransferCase115Day()
부과기준일 결정
int dayCnt = TB_CAR_FFNLG_TRGT.DAYCNT;
if (dayCnt > 115) {
// 이첩-2
부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자.plusDays(115);
} else {
// 이첩-1
부과기준일 = TB_CAR_FFNLG_TRGT.검사일자;
}
법정동코드 비교
// 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
String userOrg4 = userInfo.getOrgCd().substring(0, 4);
if (!legalDong4.equals(userOrg4)) {
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER; // 이첩
}
결과 처리
| 구분 | 조건 | 비고 컬럼 형식 |
|---|---|---|
| 이첩-1 | DAYCNT <= 115 |
"서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" |
| 이첩-2 | DAYCNT > 115 |
"전라남도 순천시 / 김정대, 115일 도래지, [2개의 api 법정동코드 및 법정동명]" |
처리 흐름도
시작
│
▼
[차량번호 조회]
│
▼
┌─────────────────────────────────────┐
│ 1. 상품용 (검사일 소유자가 상품용) │
│ → 조건 충족: 상품용(02) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
┌─────────────────────────────────────┐
│ 2. 내사종결 - 명의이전 전 상품용 │
│ (31일 이내) │
│ → 조건 충족: 내사종결(04) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
┌─────────────────────────────────────┐
│ 3. 내사종결 - 순수 명의이전 │
│ (31일 이내, 상품용 아님) │
│ → 조건 충족: 내사종결(04) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
┌─────────────────────────────────────┐
│ 4. 날짜수정 - 명의이전 전 상품용 │
│ (31일 초과) │
│ → 조건 충족: 날짜수정후부과(05) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
┌─────────────────────────────────────┐
│ 5. 날짜수정 - 순수 명의이전 │
│ (31일 초과, 상품용 아님) │
│ → 조건 충족: 날짜수정후부과(05) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
┌─────────────────────────────────────┐
│ 6. 이첩 (법정동코드 불일치) │
│ → 조건 충족: 이첩(03) │
└─────────────────────────────────────┘
│ (조건 미충족)
▼
[다음 차량]
요약 정리
비교 로직 실행 순서
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 |
구현 완료 상태
✅ 1. 상품용 로직
- 메서드:
checkProductUse() - 처리상태코드: 02
✅ 2. 내사종결 - 명의이전 전 소유자 상품용 (31일 이내)
- 메서드:
checkInvestigationClosedByProductUseWithin31Days() - 처리상태코드: 04
✅ 3. 내사종결 - 순수 명의이전 (31일 이내)
- 메서드:
checkInvestigationClosedByOwnerChangeWithin31Days() - 처리상태코드: 04
- 특징: 명의이전 직전 소유자도 상품용 아님
✅ 4. 날짜 수정 후 부과 - 명의이전 전 소유자 상품용 (31일 초과)
- 메서드:
checkDateModifiedLevyByProductUseOver31Days() - 처리상태코드: 05
✅ 5. 날짜 수정 후 부과 - 순수 명의이전 (31일 초과)
- 메서드:
checkDateModifiedLevyByOwnerChangeOver31Days() - 처리상태코드: 05
- 특징: 명의이전 직전 소유자도 상품용 아님
✅ 6. 이첩 로직
- 메서드:
checkTransferCase115Day() - 처리상태코드: 03
구현 시 주의사항
공통 주의사항
- API 호출 순서 준수: 각 검증 단계별로 필요한 API만 호출
- 조건 우선순위: 1 → 2 → 3 → 4 → 5 → 6 순서로 검증
- 조기 종료: 조건 충족 시 즉시 다음 차량으로 이동
- 비고 컬럼: 각 조건별 정해진 형식으로 기록
- 날짜 형식 지원: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리
- 일수 계산 기준: DAYS_THRESHOLD = 31일 (상수 사용)
순수 명의이전 케이스 (3, 5번) 주의사항
- 상품용 체크: Step 4에서 명의이전 직전 소유자가 상품용이면 return null
- 상품용이면 이미 2번 또는 4번에서 처리되어야 함
- 로직 순서 중요: 상품용 케이스(2, 4번)보다 반드시 뒤에 실행
- DB 저장값: step4OwnerName을 CAR_BSC_MTTR_INQ_FLNM에 저장