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

46 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 실제 소스 코드 기준으로 전면 재작성 Javadoc과 실제 로직 차이 명시
2025-12-02 내사종결 로직 추가 및 상품용 조건 변경 명의이전 31일 이내 조건 추가
2025-01-XX 상품용 로직 완전 변경 API 호출 4단계, 소유자명 일치 확인 추가

처리 규칙

중요: 순서가 중요함!

  1. 상품용 → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 날짜수정(상품용) → 5. 날짜수정(명의이전) → 6. 이첩
  • 조건에 걸리는 순간 다음 차량번호 비교로 진행
  • 각 비교 로직별로 개별 API 호출 수행

비교 로직 상세

1. 상품용 검증 - 검사일 소유자가 상품용

처리상태코드: 02 (상품용) 메서드: checkProductUse()

Javadoc 주석 내용

/**
 * 1. 상품용 검증 api-1번호출.소유자명.contains("상품용")
 *
 * API 호출 4단계:
 * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명
 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드
 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List
 * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD) → 해당 시점의 소유자명
 *
 * 비교조건:
 * - 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개의 조건은 주석처리
 */

API 호출 순서

순서 API 입력 파라미터 출력 데이터 용도
1 자동차기본정보 차량번호, 부과일자=검사일 차대번호(vin), 소유자명(step1OwnerName) 검사일 기준 소유자 확인
2 자동차기본정보 1.차대번호, 부과일자=오늘일자 차량번호, 성명, 민원인주민번호, 민원인법정동코드 현재 소유자 정보
3 자동차등록원부(갑) 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 갑부 상세 List 명의이전 이력 조회
4 자동차기본정보 1.차대번호, 부과일자=조건에맞는CHG_YMD 소유자명(step4OwnerName) 명의이전 시점 소유자 확인

실제 비교 조건 로직 (소스코드 기준)

// 조건 1: Step 1 소유자명에 "상품용" 포함 여부
if (step1OwnerName == null || !step1OwnerName.contains("상품용")) {
    log.debug("[상품용] 검사일 소유자명에 '상품용' 미포함 - 차량번호: {}", vhclno);
    return null;  // 미충족
}

// 조건 2: Step 3 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기
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();

    // 조건: 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);
    }
}

if (targetRecord == null) {
    log.debug("[상품용] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
    return null;
}

// 조건 3: Step 4 소유자명 == Step 1 소유자명 (명의이전 시점 소유자명 == 검사일 기준 소유자명)
if (step4OwnerName == null || !step4RprsvOwnrIdecno.equals(step1RprsvOwnrIdecno)) {
    log.debug("[상품용] 소유자명 불일치 - Step1 소유자: {}, Step4 소유자: {}", step1RprsvOwnrIdecno, step4RprsvOwnrIdecno);
    return null;
}

// 주석처리된 조건 4: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내
/*
if (!DateUtil.isDateBetween(targetChgYmd, vldPrdExpryYmd, inspEndYmd)) {
    log.debug("[상품용] CHG_YMD가 유효기간만료일~검사종료일자 범위 밖 - CHG_YMD: {}, 범위: {} ~ {}",
              targetChgYmd, vldPrdExpryYmd, inspEndYmd);
    return null;
}
*/

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE;  // "02" (상품용 조건 충족)

로직 설명

검사일 당시 소유자가 상품용이고, 명의이전 시점에도 동일하게 상품용이었던 경우

  1. 검사일 소유자가 상품용: Step 1 API에서 조회한 소유자명에 "상품용" 포함
  2. 명의이전 레코드 검색: Step 3 갑부 상세에서 다음 조건을 만족하는 레코드 찾기
    • CHG_TASK_SE_CD == "11" (명의이전)
    • CHG_YMD <= 검사종료일자
    • 가장 마지막 일자 (여러 개면 최신 날짜)
  3. 명의이전 시점 소유자 확인: Step 4 API로 명의이전 시점(CHG_YMD) 소유자명 조회
  4. 소유자명 일치 여부: Step 1 소유자명 == Step 4 소유자명

결과 처리

  • 업무 처리 상태 코드: 02 (상품용)
  • 비고 컬럼 형식:
상품용 - 상품용검사
  • 메서드: ComparisonRemarkBuilder.buildProductUseRemarkCase1()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd("02");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(step4OwnerName);  // 명의이전 시점 소유자명
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());  // "11"
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());  // "명의이전"
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());  // 명의이전 변경일자
existingData.setCarRegFrmbkDtl(갑부레코드상세);
existingData.setRmrk(비고);

2. 내사종결 검증 - 명의이전 전 소유자 상품용 (31일 이내)

처리상태코드: 04 (내사종결) 메서드: checkInvestigationClosedByProductUseWithin31Days()

Javadoc 주석 내용

/**
 * 2. 내사종결 검증 - 명의이전 이전소유자 상품용, 31일 이내
 *
 * API 호출 4단계:
 * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명
 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드
 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List
 * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD - 1 day) → 해당 시점의 소유자명
 *
 * 비교조건:
 * - 1단계: 소유자명에 "상품용" 이 아닐 경우 포함
 * - 2단계: 갑부 상세에서 다음 조건을 만족하는 명의이전(코드 11) 레코드 찾기
 *   • CHG_YMD <= 검사일자
 *   • 현재 주석처리 CHG_YMD가 검사일 이전일자 중 가장 마지막 일자
 *   • CHG_TASK_SE_CD == "11" (명의이전)
 *   • 명의이전일자가 검사일의 31일이내
 * - 3단계: 4번 API 조회 !소유자명.contains("상품용") return null, 상품용만 최종 통과
 */

API 호출 순서

순서 API 입력 파라미터 출력 데이터 용도
1 자동차기본정보 차량번호, 부과일자=검사일 차대번호, 소유자명 검사일 기준 소유자 확인
2 자동차기본정보 1.차대번호, 부과일자=오늘일자 차량번호, 성명, 민원인주민번호, 민원인법정동코드 현재 소유자 정보
3 자동차등록원부(갑) 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 갑부 상세 List 명의이전 이력 조회
4 자동차기본정보 1.차대번호, 부과일자=CHG_YMD-1일 소유자명 명의이전 직전 소유자 확인

실제 비교 조건 로직 (소스코드 기준)

// 조건 1: Step 1 소유자명에 "상품용" 미포함 (검사일 당시 상품용 아님)
if (step1OwnerName == null || step1OwnerName.contains("상품용")) {
    log.debug("[내사종결-상품용31일이내] 검사일 소유자가 상품용이거나 null - 차량번호: {}", vhclno);
    return null;  // 상품용은 1번에서 처리됨
}

// 조건 2: Step 3 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기
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();

    // CHG_TASK_SE_CD == "11" (명의이전)
    if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
        continue;
    }

    LocalDate chgDate = DateUtil.parseDate(chgYmd);
    if (chgDate == null) {
        continue;
    }

    // CHG_YMD <= 검사일자 (Javadoc에는 "검사종료일자"라고 되어 있으나 실제는 "검사일자")
    LocalDate inspEndDate = DateUtil.parseDate(inspYmd);  // inspYmd 사용!
    if (chgDate.isAfter(inspEndDate)) {
        log.debug("[내사종결-상품용31일이내] CHG_YMD > 검사일 - 변경일자: {}, 검사일: {}", chgYmd, inspYmd);
        continue;
    }

    // 가장 마지막 일자 찾기
    if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
        latestChgDate = chgDate;
        targetRecord = record;
        log.debug("[내사종결-상품용31일이내] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
    }
}

if (targetRecord == null) {
    log.debug("[내사종결-상품용31일이내] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
    return null;
}

// 조건 3: 명의이전일자가 검사일의 31일 이내
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
    log.debug("[내사종결-상품용31일이내] 일수 범위 벗어남 - 일수: {}일 (기준: 0~31일)", daysBetween);
    return null;  // 31일 초과
}

// 조건 4: Step 4 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 "상품용" 포함
if (step4OwnerName == null || !step4OwnerName.contains("상품용")) {
    log.debug("[내사종결-상품용31일이내] 명의이전 직전 소유자가 상품용 아님 - 소유자: {}", step4OwnerName);
    return null;  // 명의이전 직전 소유자가 상품용이 아님
}

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;  // "04"

로직 설명

검사일 당시에는 상품용이 아니었지만, 명의이전 이전(31일 이내)에는 상품용이었던 경우

  1. 검사일 소유자가 상품용 아님: Step 1 소유자명에 "상품용" 미포함
  2. 검사일 이전 명의이전 발생: Step 3 갑부 상세에서 CHG_YMD <= 검사일자인 명의이전(코드 11) 중 가장 마지막 일자
  3. 31일 이내 체크: 명의이전일자 ~ 검사일 사이 일수가 0~31일
  4. 명의이전 직전 소유자가 상품용: Step 4 API로 CHG_YMD - 1일 조회, 소유자명에 "상품용" 포함

결과 처리

  • 업무 처리 상태 코드: 04 (내사종결)
  • 비고 컬럼 형식:
명의이전(25.9.3.) 이전소유자 상품용
22루2283
 - 검사기간: 2024-08-01 - 2024-08-31
 - 검사일: 2024-08-15
 - 명의이전: 2024-09-03
 - 상품용: 2024-09-03
일수차이: 19일
  • 메서드: ComparisonRemarkBuilder.buildCloseProductUseRemark()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd("04");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(step4OwnerName);  // 명의이전 직전 소유자명
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(갑부레코드상세);
existingData.setRmrk(비고);

3. 내사종결 검증 - 순수 명의이전 (31일 이내)

처리상태코드: 04 (내사종결) 메서드: checkInvestigationClosedByOwnerChangeWithin31Days()

Javadoc 주석 내용

/**
 * 3. 내사종결 검증 - 순수 명의이전 (31일 이내)
 *
 * API 호출 4단계:
 * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명
 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드
 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List
 * 4) 자동차기본정보(차대번호, 부과일자=CHG_YMD - 1일) → 명의이전 직전 소유자명
 *
 * 비교조건:
 * - 1단계: 검사일 소유자명에 "상품용" 미포함 (상품용은 1번에서 처리)
 * - 2단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기
 *   • 유효기간만료일 <= CHG_YMD <= 검사종료일자
 *   • CHG_TASK_SE_CD == "11" (명의이전)
 * - 3단계: 명의이전일자와 검사일 비교
 *   • 명의이전일자 ~ 검사일까지의 일수 계산
 *   • 31일 이내인 경우
 * - 4단계: 명의이전 직전 소유자가 상품용 아님 (상품용이면 2번에서 처리됨)
 */

API 호출 순서

순서 API 입력 파라미터 출력 데이터 용도
1 자동차기본정보 차량번호, 부과일자=검사일 차대번호, 소유자명 검사일 기준 소유자 확인
2 자동차기본정보 1.차대번호, 부과일자=오늘일자 차량번호, 성명, 민원인주민번호, 민원인법정동코드 현재 소유자 정보
3 자동차등록원부(갑) 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 갑부 상세 List 명의이전 이력 조회
4 자동차기본정보 1.차대번호, 부과일자=CHG_YMD-1일 소유자명 명의이전 직전 소유자 확인

실제 비교 조건 로직 (소스코드 기준)

// 조건 1: 검사일 소유자명에 "상품용" 미포함
if (step1OwnerName != null && step1OwnerName.contains("상품용")) {
    log.debug("[내사종결-순수명의이전31일이내] 검사일 소유자가 상품용 - 차량번호: {}", vhclno);
    return null;  // 상품용이면 1번에서 처리됨
}

// 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기
NewLedgerResponse.Record targetRecord = null;
LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);

for (NewLedgerResponse.Record record : ledgerRecords) {
    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 <= 검사종료일자
    if ((chgDate.isEqual(vldPrdExpryDate) || chgDate.isAfter(vldPrdExpryDate)) &&
            (chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) {
        targetRecord = record;
        log.debug("[내사종결-순수명의이전31일이내] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
        break;  // 첫 번째 발견된 명의이전 사용
    }
}

if (targetRecord == null) {
    log.debug("[내사종결-순수명의이전31일이내] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno);
    return null;
}

// 조건 3: 명의이전일자가 검사일의 31일 이내
LocalDate chgDate = DateUtil.parseDate(targetChgYmd);
LocalDate inspDate = DateUtil.parseDate(inspYmd);
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, inspDate);

if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
    log.debug("[내사종결-순수명의이전31일이내] 일수 범위 벗어남 - 일수: {}일 (기준: 0~31일)", daysBetween);
    return null;
}

// 조건 4: 명의이전 직전 소유자가 상품용 아님 (상품용이면 2번에서 처리됨)
if (step4OwnerName == null || step4OwnerName.contains("상품용")) {
    log.debug("[내사종결-순수명의이전31일이내] 명의이전 직전 소유자가 상품용 - 소유자: {}", step4OwnerName);
    return null;  // 상품용이면 이미 2번에서 처리됨
}

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;  // "04"

로직 설명

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

  1. 검사일 소유자가 상품용 아님: Step 1 소유자명에 "상품용" 미포함
  2. 검사기간 내 명의이전 발생: Step 3 갑부 상세에서 유효기간만료일 <= CHG_YMD <= 검사종료일자인 명의이전(코드 11)
  3. 31일 이내 체크: 명의이전일자 ~ 검사일 사이 일수가 0~31일
  4. 명의이전 직전 소유자도 상품용 아님: Step 4 소유자명에 "상품용" 미포함 (상품용이면 2번 로직에서 처리됨)

결과 처리

  • 업무 처리 상태 코드: 04 (내사종결)
  • 비고 컬럼 형식:
명의이전(25.9.3.)
22루2283
 - 검사기간: 2024-08-01 - 2024-08-31
 - 검사일: 2024-08-15
 - 명의이전: 2024-09-03
 - 상품용: 2024-09-03
일수차이: 19일
  • 메서드: ComparisonRemarkBuilder.buildOwnerChangeRemark()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd("04");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(step4OwnerName);  // 명의이전 직전 소유자명
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(갑부레코드상세);
existingData.setRmrk(비고);

4. 날짜 수정 후 부과 검증 - 명의이전 전 소유자 상품용 (31일 초과)

처리상태코드: 05 (날짜 수정 후 부과) 메서드: checkDateModifiedLevyByProductUseOver31Days()

Javadoc 주석 내용

/**
 * 4. 날짜 수정 후 부과 검증 - 명의이전 이전소유자 상품용, 31일 초과
 *
 * API 호출 4단계:
 * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명
 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드
 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List
 * 4) 자동차기본정보(차대번호, 부과일자=조건에 맞는 CHG_YMD - 1 day) → 해당 시점의 소유자명
 *
 * 비교조건:
 * - 1단계: 소유자명에 "상품용" 이 아닐 경우 포함
 * - 2단계: 갑부 상세에서 다음 조건을 만족하는 명의이전(코드 11) 레코드 찾기
 *   • CHG_YMD <= 검사일자
 *   • 현재 주석처리 CHG_YMD가 검사일 이전일자 중 가장 마지막 일자
 *   • CHG_TASK_SE_CD == "11" (명의이전)
 *   • 명의이전일자가 검사일의 31일 초과
 * - 3단계: 4번 API 조회 소유자명.contains("상품용")
 */

⚠️ Javadoc과 실제 로직 차이:

  • Javadoc: "3단계: 4번 API 조회 소유자명.contains('상품용')"
  • 실제 로직: 상품용 포함 체크 (step4OwnerName.contains("상품용")) - Javadoc이 맞음

API 호출 순서

순서 API 입력 파라미터 출력 데이터 용도
1 자동차기본정보 차량번호, 부과일자=검사일 차대번호, 소유자명 검사일 기준 소유자 확인
2 자동차기본정보 1.차대번호, 부과일자=오늘일자 차량번호, 성명, 민원인주민번호, 민원인법정동코드 현재 소유자 정보
3 자동차등록원부(갑) 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 갑부 상세 List 명의이전 이력 조회
4 자동차기본정보 1.차대번호, 부과일자=CHG_YMD-1일 소유자명 명의이전 직전 소유자 확인

실제 비교 조건 로직 (소스코드 기준)

// 조건 1: 검사일 소유자명에 "상품용" 미포함
if (step1OwnerName == null || step1OwnerName.contains("상품용")) {
    log.debug("[날짜수정-상품용31일초과] 검사일 소유자가 상품용이거나 null - 차량번호: {}", vhclno);
    return null;
}

// 조건 2: 갑부 상세에서 검사일 이전 가장 마지막 명의이전(11) 레코드 찾기 (2번 메서드와 동일)
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();

    // 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(inspYmd);
    if (chgDate.isAfter(inspEndDate)) {
        continue;
    }

    // 가장 마지막 일자 찾기
    if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
        latestChgDate = chgDate;
        targetRecord = record;
    }
}

if (targetRecord == null) {
    log.debug("[날짜수정-상품용31일초과] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
    return null;
}

// 조건 3: 명의이전일자가 검사일의 31일 초과
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
    log.debug("[날짜수정-상품용31일초과] 일수가 31일 이내 - 일수: {}일", daysBetween);
    return null;  // 31일 이내면 2번에서 처리됨
}

// 조건 4: Step 4 소유자명에 "상품용" 포함
if (step4OwnerName == null || !step4OwnerName.contains("상품용")) {
    log.debug("[날짜수정-상품용31일초과] 명의이전 직전 소유자가 상품용 아님 - 소유자: {}", step4OwnerName);
    return null;
}

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;  // "05"

로직 설명

명의이전 전 소유자가 상품용이지만 31일을 초과한 경우

  1. 검사일 소유자가 상품용 아님: Step 1 소유자명에 "상품용" 미포함
  2. 검사일 이전 명의이전 발생: Step 3 갑부 상세에서 CHG_YMD <= 검사일자인 명의이전(코드 11) 중 가장 마지막 일자
  3. 31일 초과: 명의이전일자 ~ 검사일 사이 일수가 31일 초과
  4. 명의이전 직전 소유자가 상품용: Step 4 소유자명에 "상품용" 포함

결과 처리

  • 업무 처리 상태 코드: 05 (날짜 수정 후 부과)
  • 비고 컬럼 형식: 2번과 동일
명의이전(25.9.3.) 이전소유자 상품용
22루2283
 - 검사기간: 2024-08-01 - 2024-08-31
 - 검사일: 2024-08-15
 - 명의이전: 2024-07-01
 - 상품용: 2024-07-01
일수차이: 45일
  • 메서드: ComparisonRemarkBuilder.buildCloseProductUseRemark()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd("05");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(갑부레코드상세);
existingData.setRmrk(비고);

5. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과)

처리상태코드: 05 (날짜 수정 후 부과) 메서드: checkDateModifiedLevyByOwnerChangeOver31Days()

Javadoc 주석 내용

/**
 * 5. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과)
 *
 * API 호출 4단계:
 * 1) 자동차기본정보(차량번호, 부과일자=검사일) → 차대번호, 소유자명
 * 2) 자동차기본정보(차대번호, 부과일자=오늘일자) → 차량번호, 성명, 민원인주민번호, 민원인법정동코드
 * 3) 자동차등록원부(갑)(차량번호, 성명, 민원인주민번호, 민원인법정동코드) → 갑부 상세 List
 * 4) 자동차기본정보(차대번호, 부과일자=CHG_YMD - 1일) → 명의이전 직전 소유자명
 *
 * 비교조건:
 * - 1단계: 검사일 소유자명에 "상품용" 미포함 (상품용은 1번에서 처리)
 * - 2단계: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(코드 11) 레코드 찾기
 *   • 유효기간만료일 <= CHG_YMD <= 검사종료일자
 *   • CHG_TASK_SE_CD == "11" (명의이전)
 * - 3단계: 명의이전일자와 검사일 비교
 *   • 명의이전일자 ~ 검사일까지의 일수 계산
 *   • 31일 초과인 경우
 * - 4단계: 명의이전 직전 소유자가 상품용 아님 (상품용이면 4번에서 처리됨)
 */

API 호출 순서

순서 API 입력 파라미터 출력 데이터 용도
1 자동차기본정보 차량번호, 부과일자=검사일 차대번호, 소유자명 검사일 기준 소유자 확인
2 자동차기본정보 1.차대번호, 부과일자=오늘일자 차량번호, 성명, 민원인주민번호, 민원인법정동코드 현재 소유자 정보
3 자동차등록원부(갑) 2.차량번호, 2.성명, 2.민원인주민번호, 2.민원인법정동코드 갑부 상세 List 명의이전 이력 조회
4 자동차기본정보 1.차대번호, 부과일자=CHG_YMD-1일 소유자명 명의이전 직전 소유자 확인

실제 비교 조건 로직 (소스코드 기준)

// 조건 1: 검사일 소유자명에 "상품용" 미포함
if (step1OwnerName != null && step1OwnerName.contains("상품용")) {
    log.debug("[날짜수정-순수명의이전31일초과] 검사일 소유자가 상품용 - 차량번호: {}", vhclno);
    return null;
}

// 조건 2: 갑부 상세에서 검사기간(유효기간만료일 ~ 검사종료일자) 내 명의이전(11) 레코드 찾기 (3번 메서드와 동일)
NewLedgerResponse.Record targetRecord = null;
LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);

for (NewLedgerResponse.Record record : ledgerRecords) {
    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 <= 검사종료일자
    if ((chgDate.isEqual(vldPrdExpryDate) || chgDate.isAfter(vldPrdExpryDate)) &&
            (chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) {
        targetRecord = record;
        break;
    }
}

if (targetRecord == null) {
    log.debug("[날짜수정-순수명의이전31일초과] 검사기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno);
    return null;
}

// 조건 3: 명의이전일자 ~ 검사일 사이 31일 초과
LocalDate chgDate = DateUtil.parseDate(targetChgYmd);
LocalDate inspDate = DateUtil.parseDate(inspYmd);
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, inspDate);

if (daysBetween <= DAYS_THRESHOLD) {
    log.debug("[날짜수정-순수명의이전31일초과] 일수가 31일 이내 - 일수: {}일", daysBetween);
    return null;  // 31일 이내면 3번에서 처리됨
}

// 조건 4: 명의이전 직전 소유자가 상품용 아님
if (step4OwnerName == null || step4OwnerName.contains("상품용")) {
    log.debug("[날짜수정-순수명의이전31일초과] 명의이전 직전 소유자가 상품용 - 소유자: {}", step4OwnerName);
    return null;  // 상품용이면 4번에서 처리됨
}

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;  // "05"

로직 설명

순수 명의이전 케이스 중 31일을 초과한 경우

  1. 검사일 소유자가 상품용 아님: Step 1 소유자명에 "상품용" 미포함
  2. 검사기간 내 명의이전 발생: Step 3 갑부 상세에서 유효기간만료일 <= CHG_YMD <= 검사종료일자인 명의이전(코드 11)
  3. 31일 초과: 명의이전일자 ~ 검사일 사이 일수가 31일 초과
  4. 명의이전 직전 소유자도 상품용 아님: Step 4 소유자명에 "상품용" 미포함 (상품용이면 4번 로직에서 처리됨)

결과 처리

  • 업무 처리 상태 코드: 05 (날짜 수정 후 부과)
  • 비고 컬럼 형식: 3번과 동일
명의이전(25.9.3.)
22루2283
 - 검사기간: 2024-08-01 - 2024-08-31
 - 검사일: 2024-08-15
 - 명의이전: 2024-07-01
 - 상품용: 2024-07-01
일수차이: 45일
  • 메서드: ComparisonRemarkBuilder.buildOwnerChangeRemark()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd("05");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(갑부레코드상세);
existingData.setRmrk(비고);

6. 이첩 검증 (이첩-1, 이첩-2)

처리상태코드: 03 (이첩) 메서드: checkTransferCase115Day()

Javadoc 주석 내용

/**
 * 6. 이첩 검증
 *
 * DAYCNT 기반 부과기준일 계산:
 * - DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일)
 * - DAYCNT <= 115: 이첩-1 (부과기준일 = 검사일자)
 *
 * 법정동코드 비교:
 * - 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
 */

부과기준일 결정

String daycntStr = existingData.getDaycnt();
if (daycntStr == null || daycntStr.isEmpty()) {
    log.warn("[이첩] DAYCNT 값이 없음 - 차량번호: {}", vhclno);
    return null;
}

int daycnt = Integer.parseInt(daycntStr);

String levyCrtrYmd;  // 부과기준일
String transferType;

if (daycnt > 115) {
    // 이첩-2: 부과기준일 = 검사종료일자 + 115일
    String inspEndYmd = existingData.getInspEndYmd();
    LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
    LocalDate levyCrtrDate = inspEndDate.plusDays(115);
    levyCrtrYmd = levyCrtrDate.format(DATE_FORMATTER);
    transferType = "이첩-2";
    log.info("[이첩-2] DAYCNT > 115 - 부과기준일 = 검사종료일자({}) + 115일 = {}", inspEndYmd, levyCrtrYmd);
} else {
    // 이첩-1: 부과기준일 = 검사일자
    levyCrtrYmd = existingData.getInspYmd();
    transferType = "이첩-1";
    log.info("[이첩-1] DAYCNT <= 115 - 부과기준일 = 검사일자({})", levyCrtrYmd);
}

API 호출

API 입력 파라미터 출력 데이터 용도
자동차기본정보 차량번호, 부과일자=부과기준일 사용본거지법정동코드(usgsrhldStdgCd) 부과기준일 기준 사용본거지 확인

법정동코드 비교

// API 호출
NewBasicRequest request = createBasicRequest(vhclno, null, levyCrtrYmd);
NewBasicResponse response = apiService.getBasicInfo(request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(response, existingData.getCarFfnlgTrgtId());

// 사용본거지법정동코드 추출
String usgsrhldStdgCd = record.getUsgsrhldStdgCd();
if (usgsrhldStdgCd == null || usgsrhldStdgCd.length() < 4) {
    log.warn("[이첩] 법정동코드 유효하지 않음 - 차량번호: {}, 법정동코드: {}", vhclno, usgsrhldStdgCd);
    return null;
}

// 세션에서 사용자 정보 조회
LoginUserVO userInfo = SessionUtil.getLoginUser();
if (userInfo == null || userInfo.getOrgCd() == null) {
    log.warn("[이첩] 사용자 정보 없음 - 차량번호: {}", vhclno);
    return null;
}

// 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교
String legalDong4 = usgsrhldStdgCd.substring(0, 4);
String userOrgCd = userInfo.getOrgCd();
String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd;

if (legalDong4.equals(userOrg4)) {
    log.debug("[이첩] 법정동코드 일치 - 법정동: {}, 조직코드: {}", legalDong4, userOrg4);
    return null;  // 일치하면 이첩 대상 아님
}

log.info("[이첩] 법정동코드 불일치 - 법정동: {}, 조직코드: {}", legalDong4, userOrg4);

// 시군구 코드 및 시군구명 조회
String sggCd = usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);

return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER;  // "03"

결과 처리

구분 조건 비고 컬럼 형식
이첩-1 DAYCNT <= 115 "{시군구명}, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리: {userOrg4}, 법정동명: {sggNm}]"
이첩-2 DAYCNT > 115 "{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"
  • 메서드: ComparisonRemarkBuilder.buildTransferRemark()

DB 업데이트 필드

existingData.setCarBassMatterInqireId(response.getGeneratedId());
existingData.setTaskPrcsSttsCd("03");
existingData.setTaskPrcsYmd(오늘일자);
existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm());  // 기존 소유자명 유지
existingData.setCarBscMttrInqSggCd(sggCd);  // 시군구 코드
existingData.setCarBscMttrInqSggNm(sggNm);  // 시군구명
existingData.setRmrk(비고);

처리 흐름도

시작
  │
  ▼
[차량번호 조회]
  │
  ▼
┌─────────────────────────────────────┐
│ 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

Javadoc 주석과 실제 로직 차이점

1. checkInvestigationClosedByProductUseWithin31Days() (2번 메서드)

Javadoc 주석:

- 3단계: 4번 API 조회 소유자명.contains("상품용")

실제 로직:

if (step4OwnerName == null || !step4OwnerName.contains("상품용")) {
    return null;  // 상품용 미포함 체크
}

차이점: Javadoc은 "상품용 포함"이라고 되어 있으나, 실제 로직은 "상품용 미포함" 체크

2. 날짜 비교 기준 (2번, 4번 메서드)

Javadoc 주석:

• CHG_YMD <= 검사일자

실제 변수명:

LocalDate inspEndDate = DateUtil.parseDate(inspYmd);  // inspYmd 사용!

차이점: 변수명은 inspEndDate지만 실제로는 inspYmd (검사일자)를 파싱함


구현 완료 상태

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

Javadoc과 실제 로직 차이

  1. 2번 메서드: Javadoc에는 "상품용 포함"이라고 되어 있으나 실제는 "상품용 미포함" 체크
  2. 날짜 비교: inspEndDate 변수명을 사용하지만 실제로는 inspYmd (검사일자) 파싱

참고 상수 및 유틸리티

상수

// 일수 기준값
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" = 명의이전

유틸리티

// 날짜 파싱 (yyyyMMdd, yyyy-MM-dd 지원)
LocalDate DateUtil.parseDate(String dateStr)

// 일수 계산
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate)

// 날짜 포맷
String DateUtil.formatDateString(String date, String format)

문서 작성 완료일: 2025-12-03 실제 소스 코드 기준: ComparisonServiceImpl.java 분석 대상 메서드: 6개 (상품용, 내사종결 2개, 날짜수정 2개, 이첩)