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.
VIPS/docs/자동차과태료_비교로직_정리.md

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단계, 소유자명 일치 확인 추가

처리 규칙

중요: 순서가 중요함!

  1. 상품용 → 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일 이내)에는 상품용이었던 경우

  1. 검사일 소유자가 상품용 아님
  2. 검사일 이전 31일 이내에 명의이전(코드 11) 발생
  3. 명의이전 직전 소유자가 상품용

결과 처리

  • 업무 처리 상태 코드: 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;

로직 설명

순수 명의이전 케이스 (상품용과 무관)

  1. 검사일 소유자가 상품용 아님
  2. 검사기간 내 명의이전(코드 11) 발생
  3. 명의이전일자가 검사일의 31일 이내
  4. 명의이전 직전 소유자도 상품용 아님 (상품용이면 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일을 초과한 경우

  1. 검사일 소유자가 상품용 아님
  2. 검사일 이전에 명의이전(코드 11) 발생
  3. 명의이전일자가 검사일의 31일 초과
  4. 명의이전 직전 소유자가 상품용

결과 처리

  • 업무 처리 상태 코드: 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일을 초과한 경우

  1. 검사일 소유자가 상품용 아님
  2. 검사기간 내 명의이전(코드 11) 발생
  3. 명의이전일자가 검사일의 31일 초과
  4. 명의이전 직전 소유자도 상품용 아님 (상품용이면 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

구현 시 주의사항

공통 주의사항

  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에 저장