Compare commits

...

21 Commits

Author SHA1 Message Date
박성영 e113a20b23 ### feat: `Step 1` 로직 공통화 및 `delay_checker` 메서드 시그니처 수정
- **`Step 1` 공통 로직 도입**
  - `ComparisonServiceImpl`에서 차대번호 기반 API 호출(`Step 1`)을 공통화:
    - `step1Response` 생성 및 응답 데이터 유효성 검증 추가.
    - 오류 처리 및 로깅(`log.warn`, `log.error`) 강화.
    - 응답 객체를 각 검사 로직에서 재사용하도록 수정.

- **`delay_checker` 메서드 시그니처 업데이트**
  - 기존 `check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response)` →
    `check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response)`로 변경:
    - 모든 `delay_checker`(`OwnerCloseWithin31Checker`, `ProductUseChecker`, `TransferCase115DayChecker` 등)에 반영.
    - 공통 API 호출(`Step 1`) 결과를 재활용하여 불필요한 중복 호출 제거.
    - `createBasicRequest`, `bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx` 처리 로직 정리.

- **로직 순서 정리 및 가독성 개선**
  - 처리 흐름 단순화:
    - 검사 단계 순서를 공통 체크 → 내사종결 → 날짜 수정 후 부과로 정리.
    - 각 체커 로직에서 호출 단계별 주석 추가.

- **`ComparisonOmServiceImpl` 로직 재정렬**
  - 명의이전 로직(`ownerTransferOmChecker`) 순서를 뒤로 이동:
    - 기존 이첩 로직 다음 단계로 조정하여 처리 가독성 강화.

- **불필요한 호출 제거**
  - `delay_checker` 내 중복된 `Step 1` 호출 제거:
    - 공통화된 `step1Response` 객체를 활용하도록 모든 로직 간소화.

- **기타**
  - 주석 및 로깅 일관성 개선:
    - 호출 단계별 로그 추가 및 기존 주석 정리.
    - `step1Response` 응답 유효성 검증 및 오류 시 로깅 처리 강화.
2 days ago
박성영 eb4a466a3c ### feat: `Step 0` 공통 로직 추가 및 delay_checker 업데이트
- **`Step 0` 공통 로직 도입**
  - `ComparisonServiceImpl`에 차량번호 기반 API 호출(`Step 0`) 공통화:
    - API 호출하여 `step0Response` 객체 생성.
    - 결과 데이터 유효성 검증 후 체크 로직에서 재사용.
    - 필요 시 null 반환 및 적절한 로그 출력(`log.warn` / `log.error`).

- **`delay_checker` 클래스 메서드 시그니처 수정**
  - 기존 `check(CarFfnlgTrgtVO existingData, String userOrgCd)` →
    `check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response)`로 변경:
    - `OwnerCloseWithin31Checker`, `ProductUseChecker`, `TransferCase115DayChecker` 등 전범위 반영.
    - `step0Response`를 활용하여 기존 중복 API 호출 제거.
    - 불필요한 `Step 0` 호출 및 데이터 매핑 로직 제거.

- **로직 개선 및 코드 정리**
  - 중복 호출 제거 및 API 응답 데이터 재사용을 위해 호출 순서 정리:
    - 공통 체크(상품용/명의이전 등) → 내사종결 → 날짜 수정 및 부과 순서로 간소화.
    - 각 단계별 로직 정리 및 부적절한 중복 호출 제거.

- **기타**
  - 불필요한 주석 제거 및 메서드 내 주석 추가로 가독성 강화.
  - `createBasicRequest` 메서드 추가로 API 호출 로직 캡슐화.
2 days ago
박성영 bccccf3966 ### feat: 명의이전 날짜 비교와 비고 생성 로직 개선
- **명의이전 날짜 비교 조건 추가**
  - `ProductLevyOver31Checker`, `ProductCloseWithin31Checker` 로직 수정:
    - `유효기간만료일 - 90일 <= 변경일자 <= 검사종료일자` 조건으로 비교 강화.
    - 기존 검사 종료일자와의 비교 조건을 대체.
    - 날짜 비교 로직 내 `vldPrdExpryDate`, `vldPrdExpryDateMinus90`, `inspEndDate` 변수를 추가하여 명확한 기준 설정.

- **비고 상세 생성 개선**
  - `ComparisonRemarkBuilder` 로직 확장:
    - `유효기간만료일`, `검사종료일자` 등을 비고 상세 정보로 추가.
    - 검색 조건, 날짜
2 days ago
박성영 045c6ec816 ### feat: 차량번호 대신 차대번호 활용 및 Step 0 로직 추가
- **차량번호 대신 차대번호 활용**
  - `OwnerCloseWithin31Checker`, `ProductLevyOver31Checker`, `ProductUseChecker` 등 여러 `delay_checker` 및 `om_checker` 클래스:
    - Step 0 단계에서 API 호출을 통해 차대번호(`vin`)를 조회하도록 수정.
    - Step 1 단계 및 이후 요청에서는 차량번호 대신 차대번호를 사용.

- **Step 0 로직 추가**
  - 명의이전 및 상품용 지연 케이스 모두 Step 0 단계 신설:
    - API 호출로 차대번호 조회.
    - 응답 데이터 유효성 검증 추가:
      - 응답 데이터가 없거나 비정상이면 null 반환.
    - 로깅(`log.info` 및 `log.warn`) 강화:
      - 오류나 응답 없음 등 상태에 따라 적절히 로그 출력.
    - `bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx` 호출로 로그 데이터 초기화.

- **JSP 검색 조건 로직 수정**
  - 검색 조건에서 날짜 포맷 관련 `.replace("-", "")` 제거:
    - 원본 값을 그대로 활용.
    - `searchCond.schRcptYmdStart`, `searchCond.schRcptYmdEnd` 등 검색 타임라인 유지.

- **서비스 로직 유지보수**
  - `inspYmd` 또는 `levyCrtrYmd`와 같은 기존 요청 포맷에 따라 Step 0의 차대번호를 전달하도록 기존 메서드 호출 방식 수정.
  - 불필요한 중복 호출 및 데이터 매핑 로직 제거.

- **기타**
  - 공통화된 API 호출 및 처리 로직 반영.
  - 관련 로직 가독성 강화를 위한 주석 및 변수명 수정.
2 days ago
박성영 11456d7340 ### refactor: 날짜 형식 처리 로직 및 데이터 저장 변경
- **`OwnerTransferOmChecker` 및 `ProductUseOmChecker` 클래스 수정**
  - 날짜 데이터에서 불필요한 `.replace("-", "")` 호출 제거 및 기존 포맷 그대로 사용하도록 변경.
  - 날짜 형식을 처리하기 위해 `inspVldPrd`, `levyCrtrYmd` 등 필드에 처리 로직 조정.
  - `DATE_FORMATTER` → `DATE_FORMATTER_DASH` 상수로 변경하여 일관성 확보.

- **데이터 저장 로직 수정**
  - `taskPrcsYmd`, `carRegFrmbkChgYmd` 데이터에 기존 포맷 유지:
    - `LocalDate.now().format(DATE_FORMATTER_DASH)` 호출로 저장 포맷 명확화.
    - 기존 `.replace("-", "")` 변환 로직 제거하여 데이터 원본 그대로 유지.

- **유지보수성 향상**
  - 코드 중복 제거 및 주석 추가로 가독성 및 유지보수성 강화.
  - 기존 메서드에서 불필요한 호출 로직 제거 및 필요한 부분만 변수 재활용.

- **기타**
  - 기존 데이터 처리 로직과 연계, 호환성 확인.
3 days ago
박성영 33fcb754fc ### feat: 날짜 형식 개선 및 불필요한 페이징 제거
- **날짜 형식 포맷 통일**
  - 기존 `yyyyMMdd` → `yyyy-MM-dd` 포맷 변경.
  - `AbstractComparisonChecker` 및 `AbstractComparisonOmChecker`에 `DATE_FORMATTER_DASH` 상수 추가.
  - 모든 `delay_checker` 및 `om_checker` 클래스에서 `DATE_FORMATTER_DASH` 적용.

- **JSP 초기값 수정**
  - `schRcptYmdStart` 검색 기간 초기값 조정:
    - 기존 `-15일` → `-10일`로 변경.
  - 초기화 버튼 클릭 시 변경된 초기값 반영.

- **페이징 UI 및 로직 제거**
  - 불필요한 페이징 관련 코드 삭제:
    - JSP에서 `perPageSelect`, `currentPage`, `totalPages` 삭제.
    - JS에서 관련 전역 변수(`GRID_PAGINATION_INFO`) 및 `setOptPageOptions` 코드 제거.
    - `readData` 메서드 호출 시 항상 첫 페이지(`1`) 로드하도록 변경.
  - 그리드에서 일련번호 계산 단순화:
    - 기존 전체 페이지 기반 → 현재 페이지의 키 값만 사용.

- **DB 변경**
  - `tb_car_ffnlg_trgt_incmp` 스키마에서 날짜 필드 길이 확장:
    - `RCPT_YMD`, `TASK_PRCS_YMD`, `CAR_REG_FRMBK_CHG_YMD` 등 날짜 관련 필드 길이 `8 → 10`.

- **기타**
  - JSP 및 JS에서 사용하지 않는 포맷팅 코드 제거:
    - `moment()` 호출 비활성화 및 주석화.
3 days ago
박성영 7051953c82 ### feat: 재검 여부 필터 추가 및 그리드 개선
- **재검 여부 필터 추가**
  - `검색 조건`에 재검 여부(`재검(Y)`, `미재검(N)`) 선택 기능 추가.
  - 재검 여부에 따른 데이터 필터링 로직 구현:
    - JSP: 검색 영역에 재검 여부 체크박스 추가.
    - JS: 검색 조건(`schReinspYn`)에 재검 여부 값 추가 및 다운로드 URL에 포함되도록 설정.
    - MyBatis: 쿼리에 재검 여부 필터 추가.

- **그리드 컬럼 및 데이터 처리 개선**
  - 결과 그리드에 `재검 여부` 열 추가:
    - JSP 및 그리드 설정에 관련 컬럼 반영.
    - MyBatis 또는 데이터 처리 단계에서 `재검 여부` 값 처리 및 가공 로직 구현.
  - 일련번호 계산 로직 단순화:
    - 기존 페이징 정보 활용 대신 로우 키만 활용하도록 변경.

- **서비스 및 내부 로직 변경**
  - 데이터 처리 단계에서 `재검 여부` 설정:
    - `ServiceImpl`: 일수(`daycnt`) 값에 `*` 포함 여부로 재검 여부 결정 후 저장.
  - VO 및 Mapper 수정:
    - `CarFfnlgTrgtVO`, `CarFfnlgTrgtExcelVO`: 재검 여부(`reinspYn`) 필드 추가.
    - DB: `tb_car_ffnlg_trgt` 테이블에 `REINSP_YN` 칼럼 추가, 마이그레이션 SQL 작성.

- **불필요한 페이징 관련 코드 제거**
  - 조회 페이징용 전역 변수 및 관련 UI 제거:
    - `perPageSelect`, `currentPage`, `totalPages` 등 페이징 UI 요소 삭제.
  - JS 및 컨트롤러에서 페이징 관련 전역 로직 제거 및 단순화.

- **기타 개선**
  - 검색 초기화 버튼에 재검 여부 관련 로직 추가.
  - 검색 조건 정리(`SEARCH_COND`) 및 관련 주석 추가.
  - DEBUG 로그 추가: `재검 여부` 설정 및 출력 내역 기록.
3 days ago
박성영 9a67cf6e14 ### feat: 날짜 형식 처리 로직 수정 및 불필요한 replace 제거
- **날짜 형식 처리 개선**
  - `inspYmd`, `vldPrdExpryYmd`, `inspEndYmd` 데이터에 대해 `.replace("-", "")` 호출로 날짜 형식을 변환하도록 수정.
  - 모든 `delay_checker` 관련 클래스(`OwnerCloseWithin31Checker`, `ProductLevyOver31Checker` 등)에서 동일한 `.replace` 로직 추가하여 입력 데이터의 통일성을 보장.

- **불필요한 replace 호출 제거**
  - `CarRegFrmbkChgYmd` 필드에서 `.replace("-", "")` 호출을 제거하여 데이터 원본 그대로 사용.

- **서비스 로직 정리**
  - `ComparisonServiceImpl`:
    - 날짜 검증 순서 중 재사용성이 높은 `transferCase115DayChecker` 로직 순서를 앞쪽으로 이동.
    - 기존 순서를 재정렬하여 처리 흐름을 명확하게 개선:
      - 이첩 로직 → 내사종결 → 날짜 수정 후 부과 순서로 정리.
    - 처리 단계별 주석 추가로 가독성 개선.

- **유지보수성 강화**
  - `ComparisonServiceImpl` 및 관련 `Checker` 간 호출 순서 명확히 정리.
  - 중복 코드를 완화하고 관련 주석 및 로깅 추가.
3 days ago
박성영 06f6a03178 ### feat: 차량번호 및 최종등록일 별표 처리 로직 추가
- **차량번호 및 최종등록일 별표(`*`) 처리 로직 구현**
  - 차량번호와 최종등록일 앞에 별표가 포함된 경우, 별표를 별도의 필드(`vhclno-asterisk`, `last-reg-ymd-asterisk`)로 분리하여 처리.
  - 별표 제거 후 본 데이터와 함께 파싱 및 조립 로직 반영:
    - `CarFfnlgTrgtServiceImpl`에 별표 분리 및 데이터 처리 코드 추가.
    - 별표가 포함된 경우 다시 앞부분에 별표를 붙이는 로직 구현.

- **`application.yml` 필드 길이 설정 변경**
  - `vhclno-asterisk`, `last-reg-ymd-asterisk` 필드 추가 및 길이(1바이트) 정의.
  - 기존 필드들의 바이트 길이 수정:
    - `vhclno`: 13 → 12 (2바이트), 12 → 11 (3바이트)
    - `last-reg-ymd`: 12 → 11 (2바이트), 11 → 10 (3바이트)
    - 기타 관련 필드 길이 조정.

- **DB 스키마 변경**
  - `tb_car_ffnlg_trgt` 테이블:
    - 날짜 필드(`RCPT_YMD`, `INSP_YMD`, `LAST_REG_YMD`, 등)의 길이 기존 8 → 10으로 확장.
    - `DAYCNT` 필드 길이 4 → 5, 유효기간 만료일자(`VLD_PRD_EXPRY_YMD`) 등 관련 필드 길이 변경.

- **로깅 개선**
  - 별표 존재 여부 및 최종 데이터 확인용 DEBUG 로그 추가:
    - 차량번호 및 최종등록일 시 별표 처리 전후 내역 상세 기록.

- **유지보수성 및 가독성 향상**
  - 반복적인 고정폭 필드 처리 로직 리팩토링:
    - `padRightBytes` 호출부 정리.
    - 중복 및 불필요한 변수 제거, 관련 주석 정리.
3 days ago
박성영 545832040b ### feat: 날짜 형식 처리 로직 수정 및 검증 로직 제거
- **날짜 형식 처리 방식 변경**
  - 기존 YYYYMMDD → YYYY-MM-DD 변환 로직 제거:
    - JSP 화면, Excel 다운로드, DB 쿼리에서 날짜 변환 없이 원본 날짜 값을 그대로 사용하도록 수정.
    - `formatYmd` 및 관련 변환 메서드 제거.

- **날짜 형식 검증 로직 비활성화**
  - 입력 데이터에서 '*' 포함된 특수 형식이 있는 경우를 고려하여 날짜 검증 로직 삭제:
    - `isValidDate` 호출 제거 및 주석 처리.
    - 검사일자, 검사종료일자, 유효기간만료일자 등에 대한 검증 로직 비활성화.

- **DB 스키마 변경**
  - `tb_car_ffnlg_trgt`, `tb_car_ffnlg_trgt_incmp` 테이블:
    - `RMRK_DTL` 컬럼 타입 `MODIFY COLUMN`으로 변경.

- **MyBatis 매퍼 수정**
  - `CarFfnlgTrgtMapper_maria.xml`:
    - SELECT 구문에서 DATE_FORMAT 제거, 원본 날짜 필드 그대로 반환하도록 수정.

- **서비스 로직 개선**
  - `CarFfnlgTrgtServiceImpl`:
    - `DateTimeFormatter` 추가 및 사용하지 않는 변수를 명확히 삭제.
    - `padRightBytes`, `padLeftBytes` 호출부에서 날짜 형식 그대로 처리.

- **기타**
  - `application.yml`에서 주석 정리 및 일관성 유지.
  - 코멘트 추가 및 불필요한 변환 관련 로직 제거.
3 days ago
박성영 cadf5af5a3 ### feat: 비고 상세 생성 로직 개선 및 데이터 처리 로직 확장
- **비고 상세 생성 및 업데이트**
  - `ComparisonOmRemarkBuilder` 및 `ComparisonRemarkBuilder` 수정:
    - 비고 상세 생성 시 API 호출 및 비교 과정, 판정 근거 등 추가 기록.
    - 각 판정 유형별로 세부 정보를 포함하도록 형식 개선.

- **Checker 로직 개선**
  - `OwnerCloseWithin31Checker`, `OwnerLevyOver31Checker` 등 `delay_checker` 클래스:
    - 명의이전일자 및 검사일 간 조건 비교 로직 확장.
    - 추가 쿼리 조건 및 상세한 레코드 비교 로직 포함.

- **DB 스키마 변경**
  - `tb_car_ffnlg_trgt`, `tb_car_ffnlg_trgt_incmp` 테이블:
    - `RMRK_DTL` 컬럼 타입을 `TEXT`로 변경.
    - `RMRK_DTL` 컬럼 신규 추가로 비고 상세 저장 로직을 지원.

- **API 수정**
  - `ComparisonRemarkBuilder.buildOwnerLevyOver31RemarkDetail` 메서드:
    - `vldPrdExpryYmd`, `inspEndYmd` 등 추가 파라미터 처리.
    - 검사종료일 및 유효기간만료일에 따른 추가 비교 로직 반영.

- **기타**
  - 기존 비고 생성 로직 리팩토링 및 상세 기록을 위한 주석 추가.
  - 변경 사항을 반영한 추가 SQL 파일 작성 및 테이블 변경 적용.
3 days ago
박성영 f6c70be983 ### feat: 사용자 정보 병렬 처리 지원 및 비교 로직 수정
- **병렬 처리 시 사용자 정보 전달 구현**
  - 세션 기반 사용자 정보 조회 로직 추가: `SessionUtil.getLoginUser()`.
  - 사용자 정보를 병렬 처리의 각 작업으로 전달하기 위해 메소드 서명 수정:
    - `processOneTarget`, `executeComparison`에 `LoginUserVO` 매개변수 추가.

- **Comparison 서비스 로직 업데이트**
  - 기존 세션 종속 비교 로직을 호출부에서 사용자 정보를 전달하도록 수정:
    - `ComparisonOmServiceImpl`, `ComparisonServiceImpl` 코드 변경.
    - `ComparisonOmChecker`, `delay_checker`의 호출 방식 업데이트.

- **코드 리팩토링**
  - 중복된 세션 접근 코드를 `try-catch` 처리로 통합.
  - 불필요한 세션 의존성 최소화 및 호출부 주입 방식으로 변경.

- **병렬 처리 개선**
  - `CompletableFuture` 기반 병렬 처리에 사용자 정보가 올바르게 전달되도록 수정.
  - 스레드 풀 크기 최적화: `(CPU 코어 수 * 2)`.

- **기타**
  - 주석 추가 및 로그 개선: 세션 조회 실패 경고 로그 및 사용자 정보 전달 내역 로깅.
  - 기존 트랜잭션 처리 방식과 병렬 처리 간의 연계 유지.
3 days ago
박성영 edc0aa5e89 ### feat: 사용자 조직코드 기반 비교 로직 추가 및 세션 의존성 제거
- **사용자 조직코드 추가**
  - `ComparisonChecker` 및 `ComparisonOmChecker`의 `check` 메서드에 `userOrgCd` 매개변수 추가.
  - 세션에서 사용자 정보를 가져오던 방식 제거 후, 호출부에서 조직코드를 전달하도록 변경.

- **Checker 클래스 수정**
  - `ProductUseChecker`, `OwnerCloseWithin31Checker`, `OwnerLevyOver31Checker` 등 모든 `delay_checker` 및 `om_checker` 클래스에서 새로운 매개변수(`userOrgCd`)를 활용하는 방식으로 수정.
  - 사용자 조직코드 유효성 검사 추가.

- **세션 의존성 제거**
  - 불필요한 `SessionUtil` 및 `LoginUserVO` 관련 코드 삭제.
  - 사용자 정보의 외부 주입 방식을 통해 세션 로그인을 사용하지 않는 호출도 가능하도록 개선.

- **비교 서비스 로직 수정**
  - `ComparisonServiceImpl` 및 `ComparisonOmServiceImpl`에서 세션 기반 사용자 정보 조회 후 조직코드 추출 및 모든 관련 호출로 전달.
  - 비교 로직 유지보수성 강화.

- **기타**
  - 중복 코드 제거 및 코드 정리.
  - 주석 업데이트.
3 days ago
박성영 9c6f16e4b2 ### feat: 명의이전일자 및 검사일 비교 로직 수정
- **명의이전일자 ~ 검사일 계산 조건 보완**
  - `명의이전일자 < 검사종료일` 조건을 추가하여 검사종료일에 따른 비교 로직 세분화.
  - 검사일과 명의이전일자 간 기준 일수 비교 조건 명확화:
    - 검사종료일 기준 조건 추가.
    - 명의이전일자가 검사일 내 포함되는지 확인 로직 개선.

- **적용 대상**
  - `OwnerCloseWithin31Checker`
  - `OwnerLevyOver31Checker`
  - `ProductCloseWithin31Checker`
  - `ProductLevyOver31Checker`

- **로그 레벨 조정**
  - `TransferOmChecker` 관련 `log.debug`를 `log.info`로 변경하여 로그 가시성 강화.

- **문서 업데이트**
  - `자동차과태료_비교로직_정리-[지연].md`에 관련 변경사항 반영:
    - 명의이전일자 비교 기준 및 로직 설명 수정.

- **기타**
  - 중복 코드 제거 및 가독성을 위한 리팩토링.
  - 검사일자의 파싱 처리 및 최신 명의이전일자 계산 로직 정리.
3 days ago
박성영 28fc8c4b87 ### feat: 비고(Remark) 생성 로직 개선 및 메서드 분리
- **비고 생성 메서드 수정 및 분리**
  - `ComparisonOmRemarkBuilder` 및 `ComparisonRemarkBuilder`:
    - `buildProductCloseLevyRemark` 메서드를 `buildProductCloseRemark`와 `buildProductLevyRemark`로 각각 분리.
    - 비고 형식과 요구사항에 따라 명확히 구분된 메서드 제공.
  - `OwnerTransferRemarkBuilder`와 관련된 비고 생성 메서드 개선:
    - 파라미터 추가(`sggNm`, `ownerNm`)로 상세 정보 포함 가능.

- **Checker 클래스 로직 수정**
  - `ProductLevyOver31Checker`, `ProductCloseWithin31Checker`, `OwnerTransferOmChecker`:
    - 분리된 비고 생성 메서드 호출 방식으로 변경.
    - 불필요 매개변수 제거 및 명확성 증가.

- **비고 형식 변경**
  - 31일 이내: `명의이전(YYYY.MM.DD) 이전소유자 상품용`
  - 31일 초과: `시군구명/소유자명, 미수검명의이전(YYYY.MM.DD)`

- **기타**
  - 중복된 메서드 제거 및 가독성을 위한 리팩토링.
  - 기존 로직 및 형식에 맞춰 코드 정리.
3 days ago
박성영 fb63a0bc2b ### feat: 비고 상세 생성 및 관리 기능 추가
- **주요 변경 사항**
  - `RMRK_DTL`(비고 상세) 컬럼 추가:
    - `tb_car_ffnlg_trgt`, `tb_car_ffnlg_trgt_incmp` 테이블에 `RMRK_DTL`(비고 상세, 최대 4000자) 컬럼 추가.
  - MyBatis 매퍼 및 VO 수정:
    - 관련 쿼리(`SELECT`, `INSERT`, `UPDATE`)에 `RMRK_DTL` 필드 추가.
    - `CarFfnlgTrgtVO`, `CarFfnlgTrgtExcelVO` 등 데이터 모델에 `RMRK_DTL` 속성 추가.
  - 비고 상세 생성 로직 추가:
    - `ComparisonRemarkBuilder`, `ComparisonOmRemarkBuilder`에 비고 상세 생성 메서드 다수 추가(`buildProductUseRemarkDetail` 등).
    - 로직 내 기존 `RMRK` 생성 메서드와 함께 호출하여 상세 내용을 추가 저장.
  - Checker 개선:
    - `delay_checker`, `om_checker` 등 모든 체크 로직에서 비고 상세(`RMRK_DTL`) 생성 후 DB 업데이트.

- **화면 및 UI 변경**
  - JSP 업데이트:
    - 상세 화면 및 Excel 다운로드 시 ‘비고 상세’ 열 추가.
    - ‘비고’는 너비 증가(`200px → 300px`), ‘비고 상세’는 신규 추가(`200px`).
    - 내용이 길 경우 30자 요약 표시 및 팝업 기능 추가.

- **기타**
  - SQL 정리 및 누락된 매퍼 반영.
  - 가독성 개선을 위한 코드 리팩토링.
  - 주석 및 문서 업데이트 (`자동차과태료_비교로직_정리-[미필].md`).
4 days ago
박성영 4691fb8073 ### feat: 비고(Remark) 생성 로직 개선 및 코드 단순화
- **비고 형식 변경 및 단순화**
  - 불필요한 다중 매개변수를 제거하고 단일 매개변수 처리로 변경
  - 상품용, 명의이전, 이첩 등의 비고 생성 로직에서 복잡한 문자열 빌더 사용 제거, 단순 문자열 반환 방식으로 수정

- **시군구명 및 소유자명 추가**
  - 명의이전 및 이첩 비고 생성 시 시군구명(`sggNm`)과 소유자명(`ownerNm`)을 파라미터로 추가
  - `carFfnlgTrgtMapper.selectSggNmBySggCd` 호출을 통해 시군구명 조회 로직 적용

- **불필요 메서드 삭제**
  - `NewBasicResponse` 및 `NewBasicResponse.Record` 관련 비고 생성 메서드 제거 (`buildProductUseRemark`, `buildProductUseChangeRemark` 등)

- **Checker 로직 수정**
  - `OmChecker`, `delay_checker` 내 비고 생성 로직 모두 단일화된 메서드 호출로 변경
  - 모든 비고 생성 로직에 시군구명 및 소유자명 적용
  - 호출 매개변수 최소화 및 유지보수성 강화

- **기타**
  - 기존에 사용되었던 복잡한 비고 생성 포맷 정리 및 관련 주석 제거
  - 중복 코드 제거 및 가독성을 위한 리팩토링
4 days ago
박성영 dde9bf4c02 feat: 과태료 대상 비교 로직 병렬 처리 및 트랜잭션 관리 개선
- **병렬 처리 도입**
  - 대상 데이터 처리 작업을 병렬화하여 성능 최적화
  - CPU 코어 수의 2배 스레드 풀 생성 및 `CompletableFuture` 활용

- **트랜잭션 단위 변경**
  - 기존: 전체 트랜잭션으로 관리, 하나의 실패 시 전체 롤백
  - 변경: 개별 대상별 트랜잭션 적용, 실패 데이터만 롤백

- **통계 및 로그 추가**
  - 성공/실패, 유형별 처리 건수(상품용, 이첩, 정상 등) 통계 데이터 제공
  - 병렬 처리 상태 및 작업 내역 로그 추가

- **기타**
  - `TransactionTemplate` 도입 및 가독성을 위한 코드 리팩토링
  - 문서 업데이트 (`자동차과태료_비교로직_정리-[미필].md`, `자동차과태료_비교로직_정리-[지연].md`)
4 days ago
박성영 914f61256b ### feat: 차량번호 및 소유자명 처리 로직 개선, 특수문자 제거 처리 추가
- **소유자명 검증 보완**
  - 소유자명은 null 또는 공백 입력도 가능하도록 로직 수정
  - 길이 초과(75자) 제한만 유효하게 유지

- **특수문자 '*' 제거 처리 추가**
  - 차량번호 및 일수(`daycnt`)에서 전출차량 및 재검여부를 나타내는 '*' 제거 후 검증
  - `createLedgerRequest`, `createBasicRequest` 등 차량번호를 사용하는 API 호출 로직에 적용
  - 숫자값 검증 및 비교 수행 시 특수문자 제거 처리 추가

- **차량번호 형식 검증 로직 보완**
  - '*' 특수문자 포함 여부를 허용하도록 정규식 및 검증 프로세스 수정
  - '*'만 포함된 경우는 유효하지 않도록 처리

- **Comparison 로직 수정**
  - 비교 로직 시작 시 차량번호 및 일수에서 '*' 제거 기능 추가

- **샘플 데이터 수정**
  - 샘플 데이터 파일 수정 (`docs/지연-샘플용-utf-8-1.txt`)
  - 차량번호 및 일수에서 전출차량 및 재검여부를 나타내는 '*' 적용 예시 추가

- **기타**
  - 가독성을 위한 주석 정리 및 로직 구조 개선
4 days ago
박성영 ad168358cb feat: 과태료 대상 비교 로직 병렬 처리 구현 및 성능 최적화
- **병렬 처리 도입**
  - 대상 데이터별로 처리 작업을 병렬화하여 성능 개선
  - I/O 작업 처리 시 CPU 코어 수의 2배 스레드 풀 적용

- **트랜잭션 단위 변경**
  - 기존: 전체가 하나의 트랜잭션으로 관리되어 하나의 대상 실패 시 전체 롤백 처리
  - 변경: 개별 데이터에 대해 독립적인 트랜잭션 적용, 실패 데이터만 롤백

- **통계 데이터 추가**
  - 성공/실패, 유형별 처리 건수(상품용, 이첩, 정상) 통계 제공

- **기타**
  - `TransactionTemplate` 및 `CompletableFuture` 활용
  - 병렬 처리 로그 및 예외 처리 추가
  - 코드 주석 및 가독성 개선
4 days ago
박성영 0dbd34c40c feat: API 초당 호출 제한 및 DB 스키마 수정
- **API Rate Limit 조정**
  - 초당 호출 제한(`permits-per-second`)을 5.0에서 0으로 변경 (무제한 호출 가능)

- **DB 스키마 변경**
  - `tb_car_ledger_frmbk_dtl` 테이블
    - `FLAG` 컬럼 크기를 `varchar(3)`에서 `varchar(1000)`로 확장 (데이터 저장 용량 증가)
4 days ago

@ -2,25 +2,27 @@ create table tb_car_ffnlg_trgt
(
CAR_FFNLG_TRGT_ID varchar(20) not null comment '자동차 과태료 대상 ID'
primary key,
RCPT_YMD varchar(8) null comment '접수 일자',
RCPT_YMD varchar(10) null comment '접수 일자',
INSPSTN_CD varchar(8) null comment '검사소 코드',
INSP_YMD varchar(8) null comment '검사 일자',
INSP_YMD varchar(10) null comment '검사 일자',
VHCLNO varchar(30) null comment '차량번호',
OWNR_NM varchar(75) null comment '소유자 명',
RRNO varchar(100) null comment '주민등록번호',
CAR_NM varchar(100) null comment '자동차 명',
CAR_KND varchar(100) null comment '자동차 종류',
CAR_USG varchar(100) null comment '자동차 용도',
INSP_END_YMD varchar(8) null comment '검사 종료 일자',
INSP_END_YMD varchar(10) null comment '검사 종료 일자',
DAYCNT varchar(5) null comment '일수',
REINSP_YN varchar(1) null comment '재검 여부',
FFNLG_AMT varchar(10) null comment '과태료 금액',
LAST_REG_YMD varchar(8) null comment '최종 등록 일자',
LAST_REG_YMD varchar(11) null comment '최종 등록 일자',
ADDR varchar(600) null comment '주소',
VLD_PRD_EXPRY_YMD varchar(8) null comment '유효 기간 만료 일자',
VLD_PRD_EXPRY_YMD varchar(10) null comment '유효 기간 만료 일자',
TRD_GDS varchar(100) null comment '매매 상품',
TASK_PRCS_STTS_CD varchar(2) null comment '업무 처리 상태 코드',
TASK_PRCS_YMD varchar(8) null comment '업무 처리 일자',
TASK_PRCS_YMD varchar(10) null comment '업무 처리 일자',
RMRK varchar(4000) null comment '비고',
RMRK_DTL text null comment '비고 상세',
CAR_BASS_MATTER_INQIRE_ID varchar(20) null comment '자동차 기본 사항 조회 ID',
CAR_LEDGER_FRMBK_ID varchar(20) null comment '자동차 등록 원부 갑 ID',
CAR_BSC_MTTR_INQ_FLNM varchar(75) null comment '자동차 기본 사항 조회 성명',
@ -28,7 +30,7 @@ create table tb_car_ffnlg_trgt
CAR_BSC_MTTR_INQ_SGG_NM varchar(75) null comment '자동차 기본 사항 조회 시군구 명',
CAR_REG_FRMBK_CHG_TASK_SE_CD varchar(2) null comment '자동차 등록 원부갑 변경 업무 구분 코드',
CAR_REG_FRMBK_CHG_TASK_SE_NM varchar(75) null comment '자동차 등록 원부갑 변경 업무 구분 명',
CAR_REG_FRMBK_CHG_YMD varchar(8) null comment '자동차 등록 원부갑 변경 일자',
CAR_REG_FRMBK_CHG_YMD varchar(10) null comment '자동차 등록 원부갑 변경 일자',
CAR_REG_FRMBK_DTL varchar(2000) null comment '자동차 등록 원부갑 상세',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',

@ -2,7 +2,7 @@ create table tb_car_ffnlg_trgt_incmp
(
CAR_FFNLG_TRGT_INCMP_ID varchar(20) not null comment '자동차 과태료 대상 미필 ID'
primary key,
RCPT_YMD varchar(8) null comment '접수 일자',
RCPT_YMD varchar(10) null comment '접수 일자',
PRGRM_ID varchar(10) null comment '프로그램 ID',
PRCS_YMD varchar(100) null comment '처리 일자',
OTPT_DT varchar(100) null comment '출력 일시',
@ -15,8 +15,9 @@ create table tb_car_ffnlg_trgt_incmp
USE_STRHLD_ADDR_DTL varchar(600) null comment '사용 본거지 주소 상세',
INSP_VLD_PRD varchar(30) null comment '검사 유효 기간',
TASK_PRCS_STTS_CD varchar(2) null comment '업무 처리 상태 코드',
TASK_PRCS_YMD varchar(8) null comment '업무 처리 일자',
TASK_PRCS_YMD varchar(10) null comment '업무 처리 일자',
RMRK varchar(4000) null comment '비고',
RMRK_DTL TEXT null comment '비고 상세',
CAR_BASS_MATTER_INQIRE_ID varchar(20) null comment '자동차 기본 사항 조회 ID',
CAR_LEDGER_FRMBK_ID varchar(20) null comment '자동차 등록 원부 갑 ID',
CAR_BSC_MTTR_INQ_FLNM varchar(75) null comment '자동차 기본 사항 조회 성명',
@ -24,7 +25,7 @@ create table tb_car_ffnlg_trgt_incmp
CAR_BSC_MTTR_INQ_SGG_NM varchar(75) null comment '자동차 기본 사항 조회 시군구 명',
CAR_REG_FRMBK_CHG_TASK_SE_CD varchar(2) null comment '자동차 등록 원부갑 변경 업무 구분 코드',
CAR_REG_FRMBK_CHG_TASK_SE_NM varchar(75) null comment '자동차 등록 원부갑 변경 업무 구분 명',
CAR_REG_FRMBK_CHG_YMD varchar(8) null comment '자동차 등록 원부갑 변경 일자',
CAR_REG_FRMBK_CHG_YMD varchar(10) null comment '자동차 등록 원부갑 변경 일자',
CAR_REG_FRMBK_DTL varchar(2000) null comment '자동차 등록 원부갑 상세',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
@ -34,3 +35,7 @@ create table tb_car_ffnlg_trgt_incmp
)
comment '자동차 과태료 대상 미필';
ALTER TABLE tb_car_ffnlg_trgt_incmp
MODIFY COLUMN RMRK_DTL TEXT NULL COMMENT '비고 상세';

@ -17,7 +17,7 @@ create table tb_car_ledger_frmbk_dtl
CHG_TASK_SE_NM varchar(75) null comment '변경 업무 구분명',
CHG_YMD varchar(10) null comment '변경 일자',
DTL_SN varchar(10) null comment '상세 순번',
FLAG varchar(3) null comment '표기여부',
FLAG varchar(1000) null comment '표기여부',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자'
)

@ -6,9 +6,9 @@
### 구현 위치
- **Checker 클래스**: `src/main/java/go/kr/project/carInspectionPenalty/registrationOm/service/impl/om_checker/`
- `ProductUseOmChecker.java` - 1. 상품용
- `OwnerTransferOmChecker.java` - 2. 명의이전 소유자 확인
- `TransferOmChecker.java` - 3. 이첩
- `ProductUseOmChecker.java` - 1. 상품용 : 상품용
- `OwnerTransferOmChecker.java` - 2. 명의이전 소유자 확인 : 명의이전(25.9.5.) 이전소유자 상품용, 경상남도 창원시/ 현대캐피탈 주식회사, 미수검명의이전(25.5.19.)(37하1553)
- `TransferOmChecker.java` - 3. 이첩 : 경기도 과천시/ 이정호, 115일 도래지
### 기본 설정
- 비교로직에 사용되는 API: `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출

@ -6,13 +6,13 @@
### 구현 위치
- **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. 이첩
- `ProductUseChecker.java` - 1. 상품용(명의이전) : 상품용
- `ProductUseChnageChecker.java` - 1-1. 상품용(변경등록) : 상품용
- `ProductCloseWithin31Checker.java` - 2. 내사종결(명의이전 이전소유자 상품용, 31일 이내) : 명의이전(25.9.11.) 이전소유자 상품용
- `OwnerCloseWithin31Checker.java` - 3. 내사종결(순수 명의이전, 31일 이내) : 명의이전(25.8.28.)
- `ProductLevyOver31Checker.java` - 4. 날짜수정후부과(명의이전 이전소유자 상품용, 31일 초과) : 경기도 고양시/ 장준혁, 미수검명의이전(25.8.19.)
- `OwnerLevyOver31Checker.java` - 5. 날짜수정후부과(순수 명의이전, 31일 초과) : 대구광역시 달서구/ 하나캐피탈(주), 미수검명의이전(25.9.3.)
- `TransferCase115DayChecker.java` - 6. 이첩 : case 1 = 경상남도 창원시/ 현대캐피탈 주식회사, 115일 도래지 case 2 = 인천광역시 부평구/ (주)우리카드, 검사일사용본거지
### 기본 설정
- 비교로직에 사용되는 API: `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출

@ -8,7 +8,7 @@
검사소 검사일자 자동차번호 소유자명 주민등록번호 차 명 차 종 용 도 종료일 일수 과태료
최종등록일 주 소 유효기간만료일 매매상품용
-------------------------------------------------------------------------------------------------------------------------------------------------
H494 2025-09-01 162고6489 (주)지앤티테크 1244110241315 엠뱅크언더리프 특수차구난형소영업용 2025-08-25 7 4만원
H494 2025-09-01 *162고6489 1244110241315 엠뱅크언더리프 특수차구난형소영업용 2025-08-25 *7 4만원
2025-07-14 경기도 용인시 기흥구 강남로 9, 111-111호(신행동, 진주만프라자) 2020-12-05
H494 2025-09-01 271구5475 (주)케이비캐피탈 1301110013499 엠뱅크언더리프 특수차구난형소영업용 2024-09-24 303 60만원

@ -96,9 +96,6 @@ public class CarFfnlgTrgtController {
// 2. totalCount 설정
paramVO.setTotalCount(totalCount);
// 3. 페이징 활성화
paramVO.setPagingYn("Y");
// 목록 조회
List<CarFfnlgTrgtVO> list = service.selectList(paramVO);

@ -62,6 +62,10 @@ public class CarFfnlgTrgtExcelVO {
@ExcelColumn(headerName = "일수", headerWidth = 10, align = ExcelColumn.Align.RIGHT)
private Long daycnt;
/** 재검여부 */
@ExcelColumn(headerName = "재검여부", headerWidth = 12, align = ExcelColumn.Align.CENTER)
private String reinspYn;
/** 과태료금액 */
@ExcelColumn(headerName = "과태료금액", headerWidth = 15, align = ExcelColumn.Align.RIGHT)
private Long ffnlgAmt;
@ -94,6 +98,10 @@ public class CarFfnlgTrgtExcelVO {
@ExcelColumn(headerName = "비고", headerWidth = 30, align = ExcelColumn.Align.LEFT)
private String rmrk;
/** 비고 상세 */
@ExcelColumn(headerName = "비고 상세", headerWidth = 50, align = ExcelColumn.Align.LEFT)
private String rmrkDtl;
/** 기본사항조회성명 */
@ExcelColumn(headerName = "기본사항조회성명", headerWidth = 18, align = ExcelColumn.Align.CENTER)
private String carBscMttrInqFlnm;

@ -35,6 +35,7 @@ public class CarFfnlgTrgtVO extends PagingVO {
private String carUsg; // 자동차 용도
private String inspEndYmd; // 검사 종료 일자
private String daycnt; // 일수
private String reinspYn; // 재검 여부 (Y/N)
private String ffnlgAmt; // 과태료 금액
private String lastRegYmd; // 최종 등록 일자
private String addr; // 주소
@ -43,6 +44,7 @@ public class CarFfnlgTrgtVO extends PagingVO {
private String taskPrcsSttsCd; // 업무 처리 상태 코드 (01=접수, 02=처리중, 03=완료)
private String taskPrcsYmd; // 업무 처리 일자
private String rmrk; // 비고
private String rmrkDtl; // 비고 상세
private String carBassMatterInqireId; // 자동차 기본 사항 조회 ID
private String carLedgerFrmbkId; // 자동차 등록 원부 갑 ID
private String carBscMttrInqFlnm; // 자동차 기본 사항 조회 성명 (상품용일 때 저장)
@ -74,6 +76,7 @@ public class CarFfnlgTrgtVO extends PagingVO {
private String schVhclno; // 검색 차량번호
private String schOwnrNm; // 검색 소유자명
private List<String> schTaskPrcsSttsCd; // 검색 업무 처리 상태 코드 (다중 선택 가능)
private List<String> schReinspYn; // 검색 재검 여부 (다중 선택 가능)
private String schInspYmdStart; // 검색 시작 검사 일자
private String schInspYmdEnd; // 검색 종료 검사 일자
private String schFfnlgTrgtSeCd; // 검색 과태료 대상 구분 코드

@ -22,7 +22,8 @@ public interface ComparisonService {
* <p> .</p>
*
* @param existingData
* @param userInfo
* @return (02=, 03=, null=)
*/
String executeComparison(CarFfnlgTrgtVO existingData);
String executeComparison(CarFfnlgTrgtVO existingData, go.kr.project.login.model.LoginUserVO userInfo);
}

@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
@ -27,6 +28,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Service
@ -40,10 +46,12 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
private final CarFfnlgTxtParseConfig parseConfig;
private final ExternalVehicleApiService service;
private final ComparisonService comparisonService;
private final TransactionTemplate transactionTemplate;
// 날짜 형식 (YYYYMMDD)
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter DATE_FORMATTER_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public int selectListTotalCount(CarFfnlgTrgtVO vo) {
return mapper.selectListTotalCount(vo);
@ -193,8 +201,8 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
// 업무 처리 상태 및 등록자 설정
vo.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT); // 01=접수
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER)); // 접수일자는 현재 날짜
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER_DASH)); // 접수일자는 현재 날짜
vo.setRgtr(rgtr);
// DB 저장
@ -272,17 +280,34 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
// 3) 데이터 라인 생성 (각 항목 2줄)
for (CarFfnlgTrgtVO row : list) {
// 차량번호 별표 처리
String vhclno = nvl(row.getVhclno());
String vhclnoAsterisk = " ";
if (vhclno.startsWith("*")) {
vhclnoAsterisk = "*";
vhclno = vhclno.substring(1); // 별표 제거
}
// 최종등록일 별표 처리
String lastRegYmd = nvl(row.getLastRegYmd());
String lastRegYmdAsterisk = " ";
if (lastRegYmd.startsWith("*")) {
lastRegYmdAsterisk = "*";
lastRegYmd = lastRegYmd.substring(1); // 별표 제거
}
// 첫째줄: 고정폭 필드들 연결
String firstLine =
padRightBytes(nvl(row.getInspstnCd()), parseConfig.getFirstLineLength("inspstn-cd"), encoding) +
padRightBytes(formatYmd(row.getInspYmd(), true), parseConfig.getFirstLineLength("insp-ymd"), encoding) +
padRightBytes(nvl(row.getVhclno()), parseConfig.getFirstLineLength("vhclno"), encoding) +
padRightBytes(row.getInspYmd(), parseConfig.getFirstLineLength("insp-ymd"), encoding) +
padRightBytes(vhclnoAsterisk, parseConfig.getFirstLineLength("vhclno-asterisk"), encoding) +
padRightBytes(vhclno, parseConfig.getFirstLineLength("vhclno"), encoding) +
padRightBytes(nvl(row.getOwnrNm()), parseConfig.getFirstLineLength("ownr-nm"), encoding) +
padRightBytes(nvl(row.getRrno()), parseConfig.getFirstLineLength("rrno"), encoding) +
padRightBytes(nvl(row.getCarNm()), parseConfig.getFirstLineLength("car-nm"), encoding) +
padRightBytes(nvl(row.getCarKnd()), parseConfig.getFirstLineLength("car-knd"), encoding) +
padRightBytes(nvl(row.getCarUsg()), parseConfig.getFirstLineLength("car-usg"), encoding) +
padRightBytes(formatYmd(row.getInspEndYmd(), true), parseConfig.getFirstLineLength("insp-end-ymd"), encoding) +
padRightBytes(row.getInspEndYmd(), parseConfig.getFirstLineLength("insp-end-ymd"), encoding) +
padLeftBytes(nvl(row.getDaycnt()), parseConfig.getFirstLineLength("daycnt"), encoding) +
padLeftBytes(formatAmtToManWon(row.getFfnlgAmt()), parseConfig.getFirstLineLength("ffnlg-amt"), encoding);
@ -291,9 +316,10 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
// 둘째줄: skip + 나머지 필드
String secondLine =
padRightBytes("", parseConfig.getSecondLineLength("skip"), encoding) +
padRightBytes(formatYmd(row.getLastRegYmd(), true), parseConfig.getSecondLineLength("last-reg-ymd"), encoding) +
padRightBytes(lastRegYmdAsterisk, parseConfig.getSecondLineLength("last-reg-ymd-asterisk"), encoding) +
padRightBytes(lastRegYmd, parseConfig.getSecondLineLength("last-reg-ymd"), encoding) +
padRightBytes(nvl(row.getAddr()), parseConfig.getSecondLineLength("addr"), encoding) +
padRightBytes(formatYmd(row.getVldPrdExpryYmd(), true), parseConfig.getSecondLineLength("vld-prd-expry-ymd"), encoding) +
padRightBytes(row.getVldPrdExpryYmd(), parseConfig.getSecondLineLength("vld-prd-expry-ymd"), encoding) +
padRightBytes(nvl(row.getTrdGds()), parseConfig.getSecondLineLength("trd-gds"), encoding);
sb.append(secondLine).append("\r\n");
@ -459,12 +485,24 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
String inspYmd = extractByteLength(firstBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 검사일자(inspYmd) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, inspYmd);
pos += len;
// 차량번호 (13바이트)
// 차량번호 별표 공간 (1바이트)
len = parseConfig.getFirstLineLength("vhclno-asterisk");
String vhclnoAsterisk = extractByteLength(firstBytes, pos, len, encoding);
log.info("[DEBUG_LOG] 차량번호 별표(vhclnoAsterisk) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, vhclnoAsterisk);
pos += len;
// 차량번호 (12바이트)
len = parseConfig.getFirstLineLength("vhclno");
String vhclno = extractByteLength(firstBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 차량번호(vhclno) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, vhclno);
pos += len;
// 별표가 있으면 차량번호 앞에 붙임
if ("*".equals(vhclnoAsterisk.trim())) {
vhclno = "*" + vhclno;
log.info("[DEBUG_LOG] 차량번호에 별표 추가: [{}]", vhclno);
}
// 소유자명 (31바이트)
len = parseConfig.getFirstLineLength("ownr-nm");
@ -501,8 +539,8 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
String inspEndYmd = extractByteLength(firstBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 종료일(inspEndYmd) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, inspEndYmd);
pos += len;
// 일수 (8바이트)
// 일수 (별표 포함)
len = parseConfig.getFirstLineLength("daycnt");
String daycnt = extractByteLength(firstBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 일수(daycnt) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, daycnt);
@ -523,12 +561,24 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
len = parseConfig.getSecondLineLength("skip");
log.info("[DEBUG_LOG] 공백 스킵 [{}바이트]", len);
pos += len;
// 최종등록일 (12바이트)
// 최종등록일 별표 공간 (1바이트)
len = parseConfig.getSecondLineLength("last-reg-ymd-asterisk");
String lastRegYmdAsterisk = extractByteLength(secondBytes, pos, len, encoding);
log.info("[DEBUG_LOG] 최종등록일 별표(lastRegYmdAsterisk) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, lastRegYmdAsterisk);
pos += len;
// 최종등록일 (11바이트)
len = parseConfig.getSecondLineLength("last-reg-ymd");
String lastRegYmd = extractByteLength(secondBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 최종등록일(lastRegYmd) [{}바이트, 위치 {}-{}] = [{}]", len, pos, pos + len, lastRegYmd);
pos += len;
// 별표가 있으면 최종등록일 앞에 붙임
if ("*".equals(lastRegYmdAsterisk.trim())) {
lastRegYmd = "*" + lastRegYmd;
log.info("[DEBUG_LOG] 최종등록일에 별표 추가: [{}]", lastRegYmd);
}
// 주소 (88바이트)
len = parseConfig.getSecondLineLength("addr");
@ -547,11 +597,12 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
String trdGds = extractByteLength(secondBytes, pos, len, encoding).trim();
log.info("[DEBUG_LOG] 매매상품(trdGds) [{}바이트, 위치 {}~끝] = [{}]", len, pos, trdGds);
// 날짜 형식 변환 (YYYY-MM-DD -> YYYYMMDD)
inspYmd = convertDateFormat(inspYmd);
inspEndYmd = convertDateFormat(inspEndYmd);
lastRegYmd = convertDateFormat(lastRegYmd);
vldPrdExpryYmd = convertDateFormat(vldPrdExpryYmd);
// 날짜에 '*' 특수문자가 붙는 경우때문에
// 날짜 형식 변환 하지않고 원본 그대로 저장 (YYYY-MM-DD -> YYYYMMDD)
//inspYmd = convertDateFormat(inspYmd);
//inspEndYmd = convertDateFormat(inspEndYmd);
//lastRegYmd = convertDateFormat(lastRegYmd);
//vldPrdExpryYmd = convertDateFormat(vldPrdExpryYmd);
log.info("[DEBUG_LOG] 날짜 변환 후 - inspYmd: [{}], inspEndYmd: [{}], lastRegYmd: [{}], vldPrdExpryYmd: [{}]",
inspYmd, inspEndYmd, lastRegYmd, vldPrdExpryYmd);
@ -559,7 +610,14 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
// 과태료 금액 숫자만 추출 (예: "30만원" -> "300000")
ffnlgAmt = extractNumericAmount(ffnlgAmt);
log.info("[DEBUG_LOG] 과태료 변환 후: [{}]", ffnlgAmt);
// 재검여부 설정 (일수에 '*'가 있으면 Y, 없으면 N)
String reinspYn = "N";
if (daycnt != null && daycnt.contains("*")) {
reinspYn = "Y";
}
log.info("[DEBUG_LOG] 재검여부(reinspYn): [{}] (일수: {})", reinspYn, daycnt);
// VO 설정
vo.setInspstnCd(inspstnCd);
vo.setInspYmd(inspYmd);
@ -571,6 +629,7 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
vo.setCarUsg(carUsg);
vo.setInspEndYmd(inspEndYmd);
vo.setDaycnt(daycnt);
vo.setReinspYn(reinspYn);
vo.setFfnlgAmt(ffnlgAmt);
vo.setLastRegYmd(lastRegYmd);
vo.setAddr(addr);
@ -686,10 +745,11 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
// 2. 검사일자 검증
if (vo.getInspYmd() == null || vo.getInspYmd().isEmpty()) {
errors.add(String.format("[데이터 %d] 검사일자가 누락되었습니다. 차량번호: %s", dataLineNumber, vhclno));
} else if (!isValidDate(vo.getInspYmd())) {
errors.add(String.format("[데이터 %d] 검사일자 형식이 올바르지 않습니다. 검사일자: %s (YYYYMMDD 형식이어야 함), 차량번호: %s",
dataLineNumber, vo.getInspYmd(), vhclno));
}
/*else if (!isValidDate(vo.getInspYmd())) {
errors.add(String.format("[데이터 %d] 검사일자 형식이 올바르지 않습니다. 검사일자: %s (YYYY-MM-DD 형식이어야 함), 차량번호: %s",
dataLineNumber, vo.getInspYmd(), vhclno));
}*/
// 3. 차량번호 검증
if (vo.getVhclno() == null || vo.getVhclno().isEmpty()) {
@ -702,12 +762,9 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
dataLineNumber, vo.getVhclno()));
}
// 4. 소유자명 검증
if (vo.getOwnrNm() == null || vo.getOwnrNm().isEmpty()) {
errors.add(String.format("[데이터 %d] 소유자명이 누락되었습니다. 차량번호: %s",
dataLineNumber, vhclno));
} else if (vo.getOwnrNm().length() > 75) {
errors.add(String.format("[데이터 %d] 소유자명이 너무 깁니다. 소유자명: %s (최대 75자), 차량번호: %s",
// 4. 소유자명 검증 (소유자명에 null or 공백 들어올수 있음 - 필수 아님)
if (vo.getOwnrNm() != null && !vo.getOwnrNm().isEmpty() && vo.getOwnrNm().length() > 75) {
errors.add(String.format("[데이터 %d] 소유자명이 너무 깁니다. 소유자명: %s (최대 75자), 차량번호: %s",
dataLineNumber, vo.getOwnrNm(), vhclno));
}
@ -756,26 +813,32 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
dataLineNumber, vo.getAddr().length(), vhclno));
}
// 8. 선택 필드 날짜 검증 (값이 있는 경우에만)
if (vo.getInspEndYmd() != null && !vo.getInspEndYmd().isEmpty() && !isValidDate(vo.getInspEndYmd())) {
errors.add(String.format("[데이터 %d] 검사종료일자 형식이 올바르지 않습니다. 검사종료일자: %s (YYYYMMDD 형식), 차량번호: %s",
dataLineNumber, vo.getInspEndYmd(), vhclno));
}
if (vo.getLastRegYmd() != null && !vo.getLastRegYmd().isEmpty() && !isValidDate(vo.getLastRegYmd())) {
errors.add(String.format("[데이터 %d] 최종등록일자 형식이 올바르지 않습니다. 최종등록일자: %s (YYYYMMDD 형식), 차량번호: %s",
dataLineNumber, vo.getLastRegYmd(), vhclno));
}
if (vo.getVldPrdExpryYmd() != null && !vo.getVldPrdExpryYmd().isEmpty() && !isValidDate(vo.getVldPrdExpryYmd())) {
errors.add(String.format("[데이터 %d] 유효기간만료일자 형식이 올바르지 않습니다. 유효기간만료일자: %s (YYYYMMDD 형식), 차량번호: %s",
dataLineNumber, vo.getVldPrdExpryYmd(), vhclno));
}
// 8. 선택 필드 날짜 검증 (값이 있는 경우에만), '*' 특수문자 들어간 경우때문에 일자형식 틀어짐, 제거
//if (vo.getInspEndYmd() != null && !vo.getInspEndYmd().isEmpty() && !isValidDate(vo.getInspEndYmd())) {
// errors.add(String.format("[데이터 %d] 검사종료일자 형식이 올바르지 않습니다. 검사종료일자: %s (YYYYMMDD 형식), 차량번호: %s",
// dataLineNumber, vo.getInspEndYmd(), vhclno));
//}
//
//if (vo.getLastRegYmd() != null && !vo.getLastRegYmd().isEmpty() && !isValidDate(vo.getLastRegYmd())) {
// errors.add(String.format("[데이터 %d] 최종등록일자 형식이 올바르지 않습니다. 최종등록일자: %s (YYYYMMDD 형식), 차량번호: %s",
// dataLineNumber, vo.getLastRegYmd(), vhclno));
//}
//
//if (vo.getVldPrdExpryYmd() != null && !vo.getVldPrdExpryYmd().isEmpty() && !isValidDate(vo.getVldPrdExpryYmd())) {
// errors.add(String.format("[데이터 %d] 유효기간만료일자 형식이 올바르지 않습니다. 유효기간만료일자: %s (YYYYMMDD 형식), 차량번호: %s",
// dataLineNumber, vo.getVldPrdExpryYmd(), vhclno));
//}
// 9. 일수 검증 (값이 있는 경우에만)
if (vo.getDaycnt() != null && !vo.getDaycnt().isEmpty() && !isNumeric(vo.getDaycnt())) {
errors.add(String.format("[데이터 %d] 일수는 숫자여야 합니다. 일수: %s, 차량번호: %s",
dataLineNumber, vo.getDaycnt(), vhclno));
// 일수에 '*' 특수문자가 포함될 수 있음 (재검여부 표시용)
if (vo.getDaycnt() != null && !vo.getDaycnt().isEmpty()) {
String daycnt = vo.getDaycnt().trim();
// '*' 제거 후 숫자 검증
String daycntWithoutAsterisk = daycnt.replace("*", "").trim();
if (!daycntWithoutAsterisk.isEmpty() && !isNumeric(daycntWithoutAsterisk)) {
errors.add(String.format("[데이터 %d] 일수는 숫자 또는 '*숫자' 형식이어야 합니다. 일수: %s, 차량번호: %s",
dataLineNumber, vo.getDaycnt(), vhclno));
}
}
return errors;
@ -820,11 +883,12 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
/**
*
*
*
* :
* - : 123456, 1234567 (2~3 + + 4 )
* - : 112222 ( + + + )
*
* - '*' ( )
*
* @param vhclno
* @return
*/
@ -832,36 +896,44 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
if (vhclno == null || vhclno.isEmpty()) {
return false;
}
// 공백 제거
vhclno = vhclno.trim();
// 최소 길이 체크 (예: 12가3456 = 7자)
if (vhclno.length() < 7) {
// '*' 특수문자를 임시로 제거하여 검증 (전출차량 등 특정 상황 표시용)
String vhclnoWithoutAsterisk = vhclno.replace("*", "");
// '*'만 있는 경우는 제외
if (vhclnoWithoutAsterisk.isEmpty()) {
return false;
}
// 최소 길이 체크 (예: 12가3456 = 7자, *12가3456 = 8자)
if (vhclnoWithoutAsterisk.length() < 7) {
return false;
}
// 패턴1: 일반 차량번호 (2~3자리 숫자 + 한글 1자 + 4자리 숫자)
// 예: 12가3456, 123가4567
if (vhclno.matches("\\d{2,3}[가-힣]\\d{4}")) {
// 예: 12가3456, 123가4567, *12가3456
if (vhclnoWithoutAsterisk.matches("\\d{2,3}[가-힣]\\d{4}")) {
return true;
}
// 패턴2: 지역명이 포함된 차량번호 (한글 + 숫자 + 한글 + 숫자)
// 예: 경기11사2222, 서울12가3456
if (vhclno.matches("[가-힣]+\\d+[가-힣]\\d+")) {
// 예: 경기11사2222, 서울12가3456, *경기11사2222
if (vhclnoWithoutAsterisk.matches("[가-힣]+\\d+[가-힣]\\d+")) {
return true;
}
// 패턴3: 특수 차량번호 (숫자 + 한글 + 숫자)
// 예: 22고2222, 33마3333
if (vhclno.matches("\\d+[가-힣]+\\d+")) {
// 예: 22고2222, 33마3333, *22고2222
if (vhclnoWithoutAsterisk.matches("\\d+[가-힣]+\\d+")) {
return true;
}
// 그 외의 경우는 일단 허용 (실제 차량번호 형식이 다양할 수 있음)
// 최소한 한글과 숫자가 모두 포함되어 있는지 확인
return vhclno.matches(".*[가-힣].*") && vhclno.matches(".*\\d.*");
return vhclnoWithoutAsterisk.matches(".*[가-힣].*") && vhclnoWithoutAsterisk.matches(".*\\d.*");
}
/**
@ -899,117 +971,188 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
}
/**
* API /
* : 1
* API / ()
* : , .
* , .
*
* @param targetList
* @return
*/
@Override
@Transactional
public Map<String, Object> compareWithApi(List<Map<String, String>> targetList) {
log.info("========== API 호출 및 비교 시작 ==========");
log.info("========== API 호출 및 비교 시작 (병렬처리) ==========");
log.info("선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);
if (targetList == null || targetList.isEmpty()) {
throw new IllegalArgumentException("선택된 데이터가 없습니다.");
}
List<Map<String, Object>> compareResults = new ArrayList<>();
int successCount = 0;
int productUseCount = 0; // 상품용 건수
int transferCount = 0; // 이첩 건수
int normalCount = 0; // 정상 처리 건수
// 세션에서 사용자 정보 조회 (병렬처리 전에 메인 스레드에서 조회)
go.kr.project.login.model.LoginUserVO userInfo = null;
try {
userInfo = SessionUtil.getLoginUser();
} catch (Exception e) {
log.warn("세션에서 사용자 정보 조회 실패", e);
}
final go.kr.project.login.model.LoginUserVO finalUserInfo = userInfo;
for (Map<String, String> target : targetList) {
String carFfnlgTrgtId = target.get("carFfnlgTrgtId");
String vhclno = target.get("vhclno");
String inspYmd = target.get("inspYmd");
String rgtr = target.get("rgtr"); // 등록자 (사용자 ID)
// I/O 작업이므로 CPU 코어 수의 2배로 스레드 풀 생성
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
log.info("처리 중 - 차량번호: {}, 검사일자: {}", vhclno, inspYmd);
log.info("병렬처리 스레드 풀 크기: {}", threadPoolSize);
Map<String, Object> compareResult = new HashMap<>();
compareResult.put("carFfnlgTrgtId", carFfnlgTrgtId);
compareResult.put("vhclno", vhclno);
try {
// 병렬로 각 건 처리
List<CompletableFuture<Map<String, Object>>> futures = targetList.stream()
.map(target -> CompletableFuture.supplyAsync(() -> processOneTarget(target, finalUserInfo), executor))
.collect(Collectors.toList());
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtVO existingData = new CarFfnlgTrgtVO();
existingData.setCarFfnlgTrgtId(carFfnlgTrgtId);
existingData = mapper.selectOne(existingData);
if (existingData == null) {
String errorMsg = String.format("기존 데이터를 찾을 수 없습니다. 차량번호: %s", vhclno);
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 모든 작업 완료 대기
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가 (전체 롤백)
if (!TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT.equals(existingData.getTaskPrcsSttsCd())) {
String errorMsg = String.format("접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s",
vhclno, existingData.getTaskPrcsSttsCd());
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 결과 수집
List<Map<String, Object>> compareResults = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 3. 비교 로직 실행
String statusCode = comparisonService.executeComparison(existingData);
// 통계 집계
int successCount = 0;
int failCount = 0;
int productUseCount = 0;
int transferCount = 0;
int normalCount = 0;
// 결과 처리
if (statusCode != null) {
// 비교 규칙이 적용됨
if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE.equals(statusCode)) {
for (Map<String, Object> result : compareResults) {
Boolean success = (Boolean) result.get("success");
if (Boolean.TRUE.equals(success)) {
successCount++;
String processStatus = (String) result.get("processStatus");
if ("상품용".equals(processStatus)) {
productUseCount++;
compareResult.put("processStatus", "상품용");
compareResult.put("message", "상품용으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER.equals(statusCode)) {
} else if ("이첩".equals(processStatus)) {
transferCount++;
compareResult.put("processStatus", "이첩");
compareResult.put("message", "이첩으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED.equals(statusCode)) {
} else if ("내사종결".equals(processStatus) || "정상".equals(processStatus)) {
normalCount++;
compareResult.put("processStatus", "내사종결");
compareResult.put("message", "내사종결로 처리되었습니다.");
} else {
normalCount++;
compareResult.put("processStatus", "기타");
compareResult.put("message", "기타 상태로 처리되었습니다.");
}
compareResult.put("success", true);
successCount++;
} else {
// 정상 처리 (비교 로직에 해당 안됨)
normalCount++;
compareResult.put("success", true);
compareResult.put("message", "정상 처리되었습니다.");
compareResult.put("processStatus", "정상");
successCount++;
failCount++;
}
} catch (Exception e) {
log.error("데이터 비교 중 오류 발생 - 차량번호: {}, 전체 롤백 처리", vhclno, e);
// 예외 재발생하여 전체 롤백 처리
throw new MessageException(e.getMessage(), e);
}
compareResults.add(compareResult);
Map<String, Object> resultData = new HashMap<>();
resultData.put("compareResults", compareResults);
resultData.put("totalCount", targetList.size());
resultData.put("successCount", successCount);
resultData.put("failCount", failCount);
resultData.put("productUseCount", productUseCount);
resultData.put("transferCount", transferCount);
resultData.put("normalCount", normalCount);
log.info("========== API 호출 및 비교 완료 (병렬처리) ==========");
log.info("전체: {}건, 성공: {}건, 실패: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건",
targetList.size(), successCount, failCount, productUseCount, transferCount, normalCount);
return resultData;
} finally {
// ExecutorService 종료
executor.shutdown();
try {
if (!executor.awaitTermination(120, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* ( )
*
* @param target
* @return
*/
private Map<String, Object> processOneTarget(Map<String, String> target, go.kr.project.login.model.LoginUserVO userInfo) {
String carFfnlgTrgtId = target.get("carFfnlgTrgtId");
String vhclno = target.get("vhclno");
String inspYmd = target.get("inspYmd");
Map<String, Object> resultData = new HashMap<>();
resultData.put("compareResults", compareResults);
resultData.put("totalCount", targetList.size());
resultData.put("successCount", successCount);
resultData.put("failCount", 0); // 1건이라도 실패하면 전체 롤백되므로 실패건수는 항상 0
resultData.put("productUseCount", productUseCount);
resultData.put("transferCount", transferCount);
resultData.put("normalCount", normalCount);
log.info("처리 중 - 차량번호: {}, 검사일자: {}", vhclno, inspYmd);
log.info("========== API 호출 및 비교 완료 ==========");
log.info("성공: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건",
successCount, productUseCount, transferCount, normalCount);
Map<String, Object> compareResult = new HashMap<>();
compareResult.put("carFfnlgTrgtId", carFfnlgTrgtId);
compareResult.put("vhclno", vhclno);
try {
// 개별 트랜잭션으로 실행
String statusCode = transactionTemplate.execute(status -> {
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtVO existingData = new CarFfnlgTrgtVO();
existingData.setCarFfnlgTrgtId(carFfnlgTrgtId);
existingData = mapper.selectOne(existingData);
if (existingData == null) {
String errorMsg = String.format("기존 데이터를 찾을 수 없습니다. 차량번호: %s", vhclno);
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가
if (!TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT.equals(existingData.getTaskPrcsSttsCd())) {
String errorMsg = String.format("접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s",
vhclno, existingData.getTaskPrcsSttsCd());
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 3. 비교 로직 실행
return comparisonService.executeComparison(existingData, userInfo);
} catch (Exception e) {
// 트랜잭션 롤백
status.setRollbackOnly();
throw e;
}
});
// 결과 처리
if (statusCode != null) {
// 비교 규칙이 적용됨
if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE.equals(statusCode)) {
compareResult.put("processStatus", "상품용");
compareResult.put("message", "상품용으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER.equals(statusCode)) {
compareResult.put("processStatus", "이첩");
compareResult.put("message", "이첩으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED.equals(statusCode)) {
compareResult.put("processStatus", "내사종결");
compareResult.put("message", "내사종결로 처리되었습니다.");
} else {
compareResult.put("processStatus", "기타");
compareResult.put("message", "기타 상태로 처리되었습니다.");
}
compareResult.put("success", true);
} else {
// 정상 처리 (비교 로직에 해당 안됨)
compareResult.put("success", true);
compareResult.put("message", "정상 처리되었습니다.");
compareResult.put("processStatus", "정상");
}
} catch (Exception e) {
log.error("데이터 비교 중 오류 발생 - 차량번호: {}", vhclno, e);
compareResult.put("success", false);
compareResult.put("message", "처리 실패: " + e.getMessage());
compareResult.put("processStatus", "실패");
}
return resultData;
return compareResult;
}
/**
@ -1064,7 +1207,7 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
List<CarFfnlgTrgtVO> updatedRows = modifyData.getUpdatedRows();
if (updatedRows != null && !updatedRows.isEmpty()) {
for (CarFfnlgTrgtVO vo : updatedRows) {
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
result += mapper.updateTaskPrcsSttsCdAndRmrk(vo);
}
}

@ -2,248 +2,556 @@ package go.kr.project.carInspectionPenalty.registration.service.impl;
import egovframework.util.DateUtil;
import egovframework.util.StringUtil;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.api.model.response.NewLedgerResponse;
/**
* (Remark)
*
* <p> .</p>
*
* <p> :</p>
* <ul>
* <li>1. : </li>
* <li>2. : (25.9.5.) , / </li>
* <li>3. -1: / , </li>
* <li>4. -2: / , 115 </li>
* </ul>
*/
public class ComparisonRemarkBuilder {
/**
* - Case 1
*
* :
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( )
* @param ledgerRecord
* @param vldPrdExpryYmd
* @param inspEndYmd
* @return
*/
public static String buildProductUseRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vldPrdExpryYmd,
String inspEndYmd) {
StringBuilder sb = new StringBuilder();
sb.append("상품용 - 상품용검사\n");
// 1. 검사일 기준 소유자 정보
sb.append("\n■ 검사일 기준 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
// 2. 명의이전 시점 소유자 정보
sb.append("\n■ 명의이전 시점 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
// 3. 갑부 상세 정보 (명의이전 이력)
sb.append("\n■ 갑부 상세 (명의이전 이력)\n");
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
//sb.append(" - 변경업무명: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeNm())).append("\n");
//sb.append(" - 접수번호: ").append(StringUtil.nvl(ledgerRecord.getAplyRcptNo())).append("\n");
// 4. 비교 기간
//sb.append("\n■ 비교 기간\n");
//sb.append(" - 유효기간만료일: ").append(DateUtil.formatDateString(vldPrdExpryYmd)).append("\n");
//sb.append(" - 검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
public static String buildProductUseRemark() {
return "상품용";
}
return sb.toString();
/**
* - (31 )
* : (25.9.11.)
*
* @param ledgerRecord ( )
* @return
*/
public static String buildProductCloseRemark(NewLedgerResponse.Record ledgerRecord) {
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
return String.format("명의이전(%s) 이전소유자 상품용", chgYmdFormatted);
}
/**
* -
* - (31 )
* : / , (25.8.19.)
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( )
* @param ledgerRecord ( )
* @param vldPrdExpryYmd
* @param inspEndYmd
* @param ledgerRecord ( )
* @param sggNm
* @param ownerNm
* @return
*/
public static String buildProductUseChangeRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vldPrdExpryYmd,
String inspEndYmd) {
public static String buildProductLevyRemark(NewLedgerResponse.Record ledgerRecord,
String sggNm, String ownerNm) {
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
return String.format("%s/ %s, 미수검명의이전(%s)",
StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm), chgYmdFormatted);
}
StringBuilder sb = new StringBuilder();
sb.append("상품용 - 변경등록\n");
/**
*
* : (25.9.5.), /
*
* @param ledgerRecord ( )
* @param sggNm
* @param ownerNm
* @return
*/
public static String buildOwnerChangeRemark(NewLedgerResponse.Record ledgerRecord,
String sggNm, String ownerNm) {
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
return String.format("명의이전(%s), %s/ %s",
chgYmdFormatted, StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm));
}
// 1. 검사일 기준 소유자 정보
sb.append("\n■ 검사일 기준 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
/**
* - Case -1 ( )
* : / ,
*
* @param sggNm
* @param ownerNm
* @return
*/
public static String buildTransferCase1Remark(String sggNm, String ownerNm) {
return String.format("%s/ %s, 검사일사용본거지", StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm));
}
// 2. 변경등록 시점 소유자 정보
sb.append("\n■ 변경등록 시점 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
/**
* - Case -2 (115 )
* : / , 115
*
* @param sggNm
* @param ownerNm
* @return
*/
public static String buildTransferCase2Remark(String sggNm, String ownerNm) {
return String.format("%s/ %s, 115일 도래지", StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm));
}
// 3. 갑부 상세 정보 (변경등록 이력)
sb.append("\n■ 갑부 상세 (변경등록 이력)\n");
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
sb.append(" - 변경업무명: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeNm())).append("\n");
sb.append(" - 특별사항: ").append(StringUtil.nvl(ledgerRecord.getSpcablMttr())).append("\n");
// ========================== 비고 상세 (RMRK_DTL) 생성 메서드 ==========================
// 4. 비교 기간
//sb.append("\n■ 비교 기간\n");
//sb.append(" - 검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
/**
*
*
* @param vhclno
* @param inspYmd
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @return
*/
public static String buildProductUseRemarkDetail(String vhclno, String inspYmd,
String step1OwnerNm, String targetChgYmd, String step4OwnerNm) {
StringBuilder sb = new StringBuilder();
sb.append("[상품용 판정 - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 검사일자(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → O\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 변경일자(CHG_YMD) ≤ 검사종료일자\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 변경일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 갑부 변경일자(").append(DateUtil.formatDateString(targetChgYmd)).append(")\n");
sb.append(" - 변경일 기준 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step4 대표소유자 회원번호 → 일치\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 검사일자 기준 소유자명에 '상품용' 포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사종료일자 이내에 존재\n");
sb.append("3. 갑부 변경일자 시점의 대표소유자 회원번호와 검사일자 기준 대표소유자 회원번호가 동일\n");
sb.append("→ 상품용 판정 (처리상태: 02_상품용)\n");
return sb.toString();
}
/**
* - Case 2 ( , )
*
* :
* (25.9.3.)
* 222283
* -
*
*
*
* -
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( = )
* @param ledgerRecord ( )
* @param vhclno
* @param inspYmd
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @param daysBetween
* @param vldPrdExpryYmd
* @param inspEndYmd
* @param daysBetween
* @return
* @return
*/
public static String buildProductCloseLevyRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vhclno,
String inspYmd,
String vldPrdExpryYmd,
String inspEndYmd,
long daysBetween) {
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
public static String buildProductCloseLevyRemarkDetail(String vhclno, String inspYmd,
String step1OwnerNm, String targetChgYmd,
String step4OwnerNm, long daysBetween,
String vldPrdExpryYmd, String inspEndYmd) {
StringBuilder sb = new StringBuilder();
sb.append("[내사종결 - 명의이전 이전소유자 상품용 판정 - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
sb.append("31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspYmd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 검사일자(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자\n");
sb.append(" - 유효기간만료일: ").append(DateUtil.formatDateString(vldPrdExpryYmd)).append("\n");
sb.append(" - 유효기간만료일-90일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n");
sb.append(" - 검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append(" - 검색 범위: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(" ~ ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자-1일(").append(DateUtil.formatDateString(
DateUtil.parseDate(targetChgYmd).minusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 명의이전 전 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: 명의이전 전 소유자명에 '상품용' 포함 → O\n\n");
sb.append("=== 일수 계산 ===\n");
sb.append("검사일~명의이전일 일수: ").append(daysBetween).append("일\n");
sb.append("기준일수: 31일\n");
sb.append("조건 확인: ").append(daysBetween).append("일 ≤ 31일 → 이내\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 검사일자 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사일자 이내에 존재\n");
sb.append("3. 명의이전일자-1일 시점의 소유자명에 '상품용' 포함\n");
sb.append("4. 명의이전일이 검사일로부터 31일 이내 (").append(daysBetween).append("일)\n");
sb.append("→ 내사종결 - 명의이전 이전소유자 상품용 판정 (처리상태: 04_내사종결)\n");
// 첫 줄: 명의이전(25.9.3.) 이전소유자 상품용
sb.append("명의이전(").append(chgYmdFormatted).append(") 이전소유자 상품용").append("\n");
// 둘째 줄: 차량번호
sb.append(StringUtil.nvl(vhclno)).append("\n");
// 셋째 줄: 검사기간 시작일자 - 종료일자
sb.append(" - 검사기간: ").append(DateUtil.formatDateString(vldPrdExpryYmd))
.append(" - ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
// 넷째 줄: 검사일 일자
sb.append(" - 검사일: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
// 다섯째 줄: 명의이전 일자
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
// 여섯째 줄: 상품용 일자 (명의이전 일자와 동일)
sb.append(" - 상품용: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
return sb.toString();
}
// 일곱째 줄: 일수차이
sb.append("일수차이: ").append(daysBetween).append("일");
/**
* -
*
* @param vhclno
* @param inspYmd
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @param daysBetween
* @return
*/
public static String buildOwnerChangeRemarkDetail(String vhclno, String inspYmd,
String step1OwnerNm, String targetChgYmd,
String step4OwnerNm, long daysBetween) {
StringBuilder sb = new StringBuilder();
sb.append("[내사종결 - 명의이전 판정 - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
sb.append("31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspYmd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 검사일자(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 변경일자(CHG_YMD) ≤ 검사일자\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자(").append(DateUtil.formatDateString(targetChgYmd)).append(")\n");
sb.append(" - 명의이전 시점 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step4 대표소유자 회원번호 → 일치\n");
sb.append(" - 명의이전 시점 소유자명에 '상품용' 포함 → X\n\n");
sb.append("=== 일수 계산 ===\n");
sb.append("검사일~명의이전일 일수: ").append(daysBetween).append("일\n");
sb.append("기준일수: 31일\n");
sb.append("조건 확인: ").append(daysBetween).append("일 ≤ 31일 → 이내\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 검사일자 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사일자 이내에 존재\n");
sb.append("3. 명의이전 시점의 대표소유자 회원번호와 검사일자 기준 대표소유자 회원번호가 동일 (소유자 변동 없음)\n");
sb.append("4. 명의이전 시점 소유자명에 '상품용' 미포함\n");
sb.append("5. 명의이전일이 검사일로부터 31일 이내 (").append(daysBetween).append("일)\n");
sb.append("→ 내사종결 - 명의이전 판정 (처리상태: 04_내사종결)\n");
return sb.toString();
}
/**
* ( )
*
* :
* (25.9.3.)
* 222283
* -
*
*
*
* -
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( = )
* @param ledgerRecord ( )
* @param vhclno
* @param inspYmd
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @param daysBetween
* @param vldPrdExpryYmd
* @param inspEndYmd
* @param daysBetween
* @return
* @return
*/
public static String buildOwnerChangeRemark(NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vhclno,
String inspYmd,
String vldPrdExpryYmd,
String inspEndYmd,
long daysBetween) {
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
public static String buildProductLevyOver31RemarkDetail(String vhclno, String inspYmd,
String step1OwnerNm, String targetChgYmd,
String step4OwnerNm, long daysBetween,
String vldPrdExpryYmd, String inspEndYmd) {
StringBuilder sb = new StringBuilder();
sb.append("[날짜수정후부과 - 명의이전 이전소유자 상품용 판정 - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
sb.append("31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspYmd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 검사일자(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자\n");
sb.append(" - 유효기간만료일: ").append(DateUtil.formatDateString(vldPrdExpryYmd)).append("\n");
sb.append(" - 유효기간만료일-90일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n");
sb.append(" - 검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append(" - 검색 범위: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(" ~ ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자-1일(").append(DateUtil.formatDateString(
DateUtil.parseDate(targetChgYmd).minusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 명의이전 전 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: 명의이전 전 소유자명에 '상품용' 포함 → O\n\n");
sb.append("=== 일수 계산 ===\n");
sb.append("검사일~명의이전일 일수: ").append(daysBetween).append("일\n");
sb.append("기준일수: 31일\n");
sb.append("조건 확인: ").append(daysBetween).append("일 > 31일 → 초과\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 검사일자 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사일자 이내에 존재\n");
sb.append("3. 명의이전일자-1일 시점의 소유자명에 '상품용' 포함\n");
sb.append("4. 명의이전일이 검사일로부터 31일 초과 (").append(daysBetween).append("일)\n");
sb.append("→ 날짜수정후부과 - 명의이전 이전소유자 상품용 판정 (처리상태: 05_날짜수정후부과)\n");
sb.append("※ 부과일자를 명의이전일자로 수정하여 재부과 필요\n");
// 첫 줄: 명의이전(25.9.3.) 이전소유자 상품용
sb.append("명의이전(").append(chgYmdFormatted).append(")").append("\n");
// 둘째 줄: 차량번호
sb.append(StringUtil.nvl(vhclno)).append("\n");
// 셋째 줄: 검사기간 시작일자 - 종료일자
sb.append(" - 검사기간: ").append(DateUtil.formatDateString(vldPrdExpryYmd))
.append(" - ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
return sb.toString();
}
// 넷째 줄: 검사일 일자
sb.append(" - 검사일: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
/**
* -
*
* @param vhclno
* @param inspYmd
* @param vldPrdExpryYmd
* @param inspEndYmd
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @param daysBetween
* @return
*/
public static String buildOwnerLevyOver31RemarkDetail(String vhclno, String inspYmd, String vldPrdExpryYmd,
String inspEndYmd, String step1OwnerNm, String targetChgYmd,
String step4OwnerNm, long daysBetween) {
StringBuilder sb = new StringBuilder();
sb.append("[날짜수정후부과 - 명의이전 판정 - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
sb.append("유효기간만료일: ").append(DateUtil.formatDateString(vldPrdExpryYmd)).append("\n");
sb.append("유효기간만료일 - 90일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n");
sb.append("검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append("31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspYmd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 검사일자(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 유효기간만료일-90일 ≤ 변경일자(CHG_YMD) ≤ 검사종료일자\n");
sb.append(" · 범위: ").append(DateUtil.formatDateString(
DateUtil.parseDate(vldPrdExpryYmd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(" ~ ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자(").append(DateUtil.formatDateString(targetChgYmd)).append(")\n");
sb.append(" - 명의이전 시점 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step4 대표소유자 회원번호 → 일치\n");
sb.append(" - 명의이전 시점 소유자명에 '상품용' 포함 → X\n\n");
sb.append("=== 일수 계산 ===\n");
sb.append("검사일~명의이전일 일수: ").append(daysBetween).append("일\n");
sb.append("기준일수: 31일\n");
sb.append("조건 확인: ").append(daysBetween).append("일 > 31일 → 초과\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 검사일자 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사기간(유효기간만료일-90일 ~ 검사종료일자) 내에 존재\n");
sb.append("3. 명의이전 시점의 대표소유자 회원번호와 검사일자 기준 대표소유자 회원번호가 동일 (소유자 변동 없음)\n");
sb.append("4. 명의이전 시점 소유자명에 '상품용' 미포함\n");
sb.append("5. 명의이전일이 검사일로부터 31일 초과 (").append(daysBetween).append("일)\n");
sb.append("→ 날짜수정후부과 - 명의이전 판정 (처리상태: 05_날짜수정후부과)\n");
sb.append("※ 부과일자를 명의이전일자로 수정하여 재부과 필요\n");
// 다섯째 줄: 명의이전 일자
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
return sb.toString();
}
// 일곱째 줄: 일수차이
sb.append("일수차이: ").append(daysBetween).append("일");
/**
* - Case -1 ( )
*
* @param vhclno
* @param inspYmd
* @param ownerNm
* @param usgsrhldStdgCd
* @param sggNm
* @param userOrgCd
* @return
*/
public static String buildTransferCase1RemarkDetail(String vhclno, String inspYmd,
String ownerNm, String usgsrhldStdgCd,
String sggNm, String userOrgCd) {
StringBuilder sb = new StringBuilder();
sb.append("[이첩 판정 - 검사일 사용본거지 (이첩-1) - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사일자: ").append(DateUtil.formatDateString(inspYmd)).append("\n");
sb.append("DAYCNT: 115일 이하\n");
sb.append("부과기준일: 검사일자 (DAYCNT ≤ 115이므로)\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과기준일(검사일자)(").append(DateUtil.formatDateString(inspYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(ownerNm)).append("\n");
sb.append(" - 사용본거지법정동코드: ").append(StringUtil.nvl(usgsrhldStdgCd)).append("\n\n");
sb.append("=== 법정동코드 비교 ===\n");
String legalDong4 = usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 4 ? usgsrhldStdgCd.substring(0, 4) : "";
String userOrg4 = userOrgCd != null && userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : "";
sb.append("사용본거지법정동코드 앞 4자리: ").append(legalDong4).append("\n");
sb.append("현재 사용자 조직코드 앞 4자리: ").append(userOrg4).append("\n");
sb.append("코드 불일치 → 이첩 대상\n");
sb.append("시군구코드: ").append(usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : "").append("\n");
sb.append("시군구명: ").append(StringUtil.nvl(sggNm)).append("\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. DAYCNT ≤ 115일이므로 부과기준일 = 검사일자\n");
sb.append("2. 검사일자 기준 사용본거지법정동코드 앞 4자리와 현재 사용자 조직코드 앞 4자리가 불일치\n");
sb.append("→ 이첩 판정 - 검사일 사용본거지 (처리상태: 03_이첩)\n");
return sb.toString();
}
/**
* - Case -1 ( )
* - Case -2 (115 )
*
* @param vhclno
* @param inspEndYmd
* @param ownerNm
* @param usgsrhldStdgCd
* @param sggNm
* @param userOrg4 4
* @return
* @param userOrgCd
* @param daycnt
* @return
*/
public static String buildTransferCase1Remark(String sggNm, String userOrg4) {
return String.format("%s, 검사일사용본거지, [사용자 조직코드 앞 4자리: %s, 법정동명: %s]",
sggNm, userOrg4, sggNm);
public static String buildTransferCase2RemarkDetail(String vhclno, String inspEndYmd,
String ownerNm, String usgsrhldStdgCd,
String sggNm, String userOrgCd, String daycnt) {
StringBuilder sb = new StringBuilder();
sb.append("[이첩 판정 - 115일 도래지 (이첩-2) - 지연]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사종료일자: ").append(DateUtil.formatDateString(inspEndYmd)).append("\n");
sb.append("DAYCNT: ").append(StringUtil.nvl(daycnt)).append("일 (115일 초과)\n");
sb.append("부과기준일 계산: 검사종료일자 + 115일 = ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspEndYmd).plusDays(115).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과기준일(검사종료일자+115일)(").append(DateUtil.formatDateString(
DateUtil.parseDate(inspEndYmd).plusDays(115).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(ownerNm)).append("\n");
sb.append(" - 사용본거지법정동코드: ").append(StringUtil.nvl(usgsrhldStdgCd)).append("\n\n");
sb.append("=== 법정동코드 비교 ===\n");
String legalDong4 = usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 4 ? usgsrhldStdgCd.substring(0, 4) : "";
String userOrg4 = userOrgCd != null && userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : "";
sb.append("사용본거지법정동코드 앞 4자리: ").append(legalDong4).append("\n");
sb.append("현재 사용자 조직코드 앞 4자리: ").append(userOrg4).append("\n");
sb.append("코드 불일치 → 이첩 대상\n");
sb.append("시군구코드: ").append(usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : "").append("\n");
sb.append("시군구명: ").append(StringUtil.nvl(sggNm)).append("\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. DAYCNT > 115일(").append(StringUtil.nvl(daycnt)).append("일)이므로 부과기준일 = 검사종료일자 + 115일\n");
sb.append("2. 부과기준일 기준 사용본거지법정동코드 앞 4자리와 현재 사용자 조직코드 앞 4자리가 불일치\n");
sb.append("→ 이첩 판정 - 115일 도래지 (처리상태: 03_이첩)\n");
return sb.toString();
}
/**
* - Case -2 (115 )
* ( 3 1300)
*
* @param sggNm
* @param legalDong4 4
* @return
* @param str
* @return
*/
public static String buildTransferCase2Remark(String sggNm, String legalDong4) {
return String.format("%s, 115일 도래지, [법정동코드: %s, 법정동명: %s]",
sggNm, legalDong4, sggNm);
private static String truncateToMaxLength(String str) {
if (str == null) {
return "";
}
// 4000바이트 / 3(한글) = 약 1333자, 여유있게 1300자로 제한
if (str.length() > 1300) {
return str.substring(0, 1297) + "...";
}
return str;
}
/**
@ -259,21 +567,14 @@ public class ComparisonRemarkBuilder {
StringBuilder detail = new StringBuilder();
// 변경 정보
StringUtil.appendIfNotEmpty(detail, "변경업무구분코드", record.getChgTaskSeCd());
StringUtil.appendIfNotEmpty(detail, "변경업무구분명", record.getChgTaskSeNm());
StringUtil.appendIfNotEmpty(detail, "변경일자", DateUtil.formatDateString(record.getChgYmd()));
// 주요 정보
StringUtil.appendIfNotEmpty(detail, "주요번호", record.getMainNo());
StringUtil.appendIfNotEmpty(detail, "일련번호", record.getSno());
StringUtil.appendIfNotEmpty(detail, "특별사항", record.getSpcablMttr());
// 명의자 정보
StringUtil.appendIfNotEmpty(detail, "명의자명", record.getHshldrNm());
StringUtil.appendIfNotEmpty(detail, "명의자식별번호", StringUtil.maskIdecno(record.getHshldrIdecno()));
// 기타
StringUtil.appendIfNotEmpty(detail, "신청접수번호", record.getAplyRcptNo());
StringUtil.appendIfNotEmpty(detail, "차량관리번호", record.getVhmno());
StringUtil.appendIfNotEmpty(detail, "원부그룹번호", record.getLedgerGroupNo());

@ -1,13 +1,22 @@
package go.kr.project.carInspectionPenalty.registration.service.impl;
import egovframework.util.SessionUtil;
import go.kr.project.api.model.request.NewBasicRequest;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.api.service.ExternalVehicleApiService;
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.ComparisonService;
import go.kr.project.carInspectionPenalty.registration.service.impl.delay_checker.*;
import go.kr.project.login.model.LoginUserVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
*
*
@ -26,66 +35,152 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co
private final OwnerLevyOver31Checker ownerLevyOver31Checker;
private final TransferCase115DayChecker transferCase115DayChecker;
private final ExternalVehicleApiService apiService;
private final VmisCarBassMatterInqireLogService bassMatterLogService;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
/**
*
*
* <p> , .</p>
*/
@Override
public String executeComparison(CarFfnlgTrgtVO existingData) {
public String executeComparison(CarFfnlgTrgtVO existingData, LoginUserVO userInfo) {
// ========== 특수문자 '*' 제거 (전출차량, 재검여부 표시용) ==========
// 차량번호에서 '*' 제거
if (existingData.getVhclno() != null) {
String cleanVhclno = existingData.getVhclno().replace("*", "");
existingData.setVhclno(cleanVhclno);
}
// 일수에서 '*' 제거
if (existingData.getDaycnt() != null) {
String cleanDaycnt = existingData.getDaycnt().replace("*", "");
existingData.setDaycnt(cleanDaycnt);
}
String vhclno = existingData.getVhclno();
log.info("========== 비교 로직 시작: {} ==========", vhclno);
// 사용자 조직코드 추출
String userOrgCd = userInfo != null ? userInfo.getOrgCd() : null;
// ========== Step 0: 자동차기본정보 조회 (차량번호 + 오늘일자) - 한번만 호출 ==========
log.info("[공통] Step 0: 자동차기본정보 조회 - 차량번호: {}, 현재일", vhclno);
NewBasicResponse step0Response = null;
try {
NewBasicRequest step0Request = createBasicRequest(vhclno, null, LocalDate.now().format(DATE_FORMATTER));
step0Response = apiService.getBasicInfo(step0Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step0Response, existingData.getCarFfnlgTrgtId());
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[공통] Step 0 응답 없음 - 차량번호: {}", vhclno);
log.info("========== 비교 로직 종료 (Step 0 응답 없음): {} ==========", vhclno);
return null;
}
} catch (Exception e) {
log.error("[공통] Step 0 호출 중 오류 발생 - 차량번호: {}", vhclno, e);
log.info("========== 비교 로직 종료 (Step 0 오류): {} ==========", vhclno);
return null;
}
// ========== Step 1: 자동차기본정보 조회 (차대번호 + 검사일) - 한번만 호출 ==========
String step0vin = step0Response.getRecord().get(0).getVin();
String inspYmd = existingData.getInspYmd().replace("-", "");
log.info("[공통] Step 1: 자동차기본정보 조회 - 차대번호: {}, 검사일: {}", step0vin, inspYmd);
NewBasicResponse step1Response = null;
try {
NewBasicRequest step1Request = createBasicRequest(null, step0vin, inspYmd);
step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[공통] Step 1 응답 없음 - 차대번호: {}", step0vin);
log.info("========== 비교 로직 종료 (Step 1 응답 없음): {} ==========", vhclno);
return null;
}
} catch (Exception e) {
log.error("[공통] Step 1 호출 중 오류 발생 - 차대번호: {}", step0vin, e);
log.info("========== 비교 로직 종료 (Step 1 오류): {} ==========", vhclno);
return null;
}
// ========== 1. 상품용 체크 - api-1번호출.소유자명.contains("상품용") ==========
String productUseResult = productUseChecker.check(existingData);
String productUseResult = productUseChecker.check(existingData, userOrgCd, step0Response, step1Response);
if (productUseResult != null) {
log.info("========== 비교 로직 종료 (상품용): {} ==========", vhclno);
return productUseResult;
}
// ========== 2. 상품용 체크 - api-1번호출.소유자명.contains("상품용-변경등록") ==========
String productUseChangeResult = productUseChnageChecker.check(existingData);
String productUseChangeResult = productUseChnageChecker.check(existingData, userOrgCd, step0Response, step1Response);
if (productUseChangeResult != null) {
log.info("========== 비교 로직 종료 (상품용-변경등록): {} ==========", vhclno);
return productUseChangeResult;
}
// ========== 3. 내사종결 체크 - 명의이전 이전소유자 상품용, 31일 이내 ==========
String investigationClosedByProductResult = productCloseWithin31Checker.check(existingData);
String investigationClosedByProductResult = productCloseWithin31Checker.check(existingData, userOrgCd, step0Response, step1Response);
if (investigationClosedByProductResult != null) {
log.info("========== 비교 로직 종료 (내사종결 - 명의이전 이전소유자 상품용, 31일 이내): {} ==========", vhclno);
return investigationClosedByProductResult;
}
// ========== 4. 내사종결 체크 - 명의이전, 31일 이내 ==========
String investigationClosedByOwnerChangeResult = ownerCloseWithin31Checker.check(existingData);
String investigationClosedByOwnerChangeResult = ownerCloseWithin31Checker.check(existingData, userOrgCd, step0Response, step1Response);
if (investigationClosedByOwnerChangeResult != null) {
log.info("========== 비교 로직 종료 (내사종결 - 명의이전, 31일 이내): {} ==========", vhclno);
return investigationClosedByOwnerChangeResult;
}
// ========== 5. 날짜 수정 후 부과 체크 - 명의이전 이전소유자 상품용, 31일 초과 ==========
String dateModifiedLevyByProductResult = productLevyOver31Checker.check(existingData);
// ========== 5. 이첩 체크 ==========
String transferResult = transferCase115DayChecker.check(existingData, userOrgCd, step0Response, step1Response);
if (transferResult != null) {
log.info("========== 비교 로직 종료 (이첩): {} ==========", vhclno);
return transferResult;
}
// ========== 6. 날짜 수정 후 부과 체크 - 명의이전 이전소유자 상품용, 31일 초과 ==========
String dateModifiedLevyByProductResult = productLevyOver31Checker.check(existingData, userOrgCd, step0Response, step1Response);
if (dateModifiedLevyByProductResult != null) {
log.info("========== 비교 로직 종료 (날짜 수정 후 부과 - 명의이전 이전소유자, 31일 초과): {} ==========", vhclno);
return dateModifiedLevyByProductResult;
}
// ========== 6. 날짜 수정 후 부과 체크 - 명의이전, 31일 초과 ==========
String dateModifiedLevyByOwnerChangeOverResult = ownerLevyOver31Checker.check(existingData);
// ========== 7. 날짜 수정 후 부과 체크 - 명의이전, 31일 초과 ==========
String dateModifiedLevyByOwnerChangeOverResult = ownerLevyOver31Checker.check(existingData, userOrgCd, step0Response, step1Response);
if (dateModifiedLevyByOwnerChangeOverResult != null) {
log.info("========== 비교 로직 종료 (날짜 수정 후 부과 - 명의이전, 31일 초과): {} ==========", vhclno);
return dateModifiedLevyByOwnerChangeOverResult;
}
// ========== 7. 이첩 체크 ==========
String transferResult = transferCase115DayChecker.check(existingData);
if (transferResult != null) {
log.info("========== 비교 로직 종료 (이첩): {} ==========", vhclno);
return transferResult;
}
log.info("========== 비교 로직 종료 (미적용): {} ==========", vhclno);
return null;
}
/**
*
*/
private NewBasicRequest createBasicRequest(String vhrno, String vin, String levyCrtrYmd) {
NewBasicRequest request = new NewBasicRequest();
NewBasicRequest.Record record = new NewBasicRequest.Record();
record.setLevyCrtrYmd(levyCrtrYmd);
if (vhrno != null) {
// API 호출 시 '*' 특수문자 제거 (전출차량 표시용)
String cleanVhrno = vhrno.replace("*", "");
record.setVhrno(cleanVhrno);
record.setInqSeCd("3"); // 3: 자동차번호
} else if (vin != null) {
record.setVin(vin);
record.setInqSeCd("2"); // 2: 차대번호
}
request.setRecord(java.util.Arrays.asList(record));
return request;
}
}

@ -26,6 +26,7 @@ public abstract class AbstractComparisonChecker implements ComparisonChecker {
protected final VmisCarLedgerFrmbkLogService ledgerLogService;
protected static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
protected static final DateTimeFormatter DATE_FORMATTER_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* ()
@ -36,7 +37,7 @@ public abstract class AbstractComparisonChecker implements ComparisonChecker {
/**
*
*
* @param vhrno
* @param vhrno ( '*' )
* @param vin
* @param levyCrtrYmd
* @return NewBasicRequest
@ -48,7 +49,9 @@ public abstract class AbstractComparisonChecker implements ComparisonChecker {
record.setLevyCrtrYmd(levyCrtrYmd);
if (vhrno != null) {
record.setVhrno(vhrno);
// API 호출 시 '*' 특수문자 제거 (전출차량 표시용)
String cleanVhrno = vhrno.replace("*", "");
record.setVhrno(cleanVhrno);
record.setInqSeCd("3"); // 3: 자동차번호
} else if (vin != null) {
record.setVin(vin);
@ -62,7 +65,7 @@ public abstract class AbstractComparisonChecker implements ComparisonChecker {
/**
* ()
*
* @param vhrno
* @param vhrno ( '*' )
* @param ownerNm
* @param idecno
* @param legalDongCd
@ -71,8 +74,9 @@ public abstract class AbstractComparisonChecker implements ComparisonChecker {
protected NewLedgerRequest createLedgerRequest(String vhrno, String ownerNm, String idecno, String legalDongCd) {
NewLedgerRequest request = new NewLedgerRequest();
// 차량번호
request.setVhrno(vhrno);
// API 호출 시 '*' 특수문자 제거 (전출차량 표시용)
String cleanVhrno = vhrno != null ? vhrno.replace("*", "") : vhrno;
request.setVhrno(cleanVhrno);
// 민원인 정보
request.setCvlprNm(ownerNm);

@ -1,5 +1,6 @@
package go.kr.project.carInspectionPenalty.registration.service.impl.delay_checker;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
/**
@ -13,7 +14,10 @@ public interface ComparisonChecker {
*
*
* @param existingData
* @param userOrgCd
* @param step0Response Step 0 API ( + )
* @param step1Response Step 1 API ( + )
* @return null ()
*/
String check(CarFfnlgTrgtVO existingData);
String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response);
}

@ -36,20 +36,24 @@ public class OwnerCloseWithin31Checker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[내사종결-명의이전] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[내사종결-명의이전] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[내사종결-명의이전] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -119,6 +123,7 @@ public class OwnerCloseWithin31Checker extends AbstractComparisonChecker {
LocalDate vldPrdExpryDateMinus90 = vldPrdExpryDate.minusDays(90);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
LocalDate latestChgDate = null;
LocalDate inspDate = DateUtil.parseDate(inspYmd);
for (NewLedgerResponse.Record record : ledgerRecords) {
String chgYmd = record.getChgYmd();
@ -156,19 +161,28 @@ public class OwnerCloseWithin31Checker extends AbstractComparisonChecker {
log.info("[내사종결-명의이전] 검사기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
// ========== 명의이전일자 ~ 검사일 사이의 일수 계산 ==========
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("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween);
return null;
// 명의이전일자 < 검사종료일
long daysBetween = 0;
if (latestChgDate.isBefore(inspEndDate)) {
// 검사일 - 검사종료일
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(inspEndDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
log.debug("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
} else {
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 이내인지 확인, (검사일자 - 마지막 명의이전일자)
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
log.debug("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
}
log.info("[내사종결-명의이전] 명의이전일자가 검사일의 {}일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween);
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
NewBasicRequest step4Request = createBasicRequest(null, vin, targetDate.format(DATE_FORMATTER));
@ -194,23 +208,29 @@ public class OwnerCloseWithin31Checker extends AbstractComparisonChecker {
log.info("[내사종결-명의이전] 명의이전 전 소유자가 상품용 아님 - 소유자명: {}", step4OwnerName);
// ========== 시군구명 조회 ==========
String usgsrhldStdgCd = step1Record.getUsgsrhldStdgCd();
String sggCd = (usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5) ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);
String ownerNm = step1Record.getRprsOwnrNm();
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark(
step1Record, step4Record, targetRecord,
vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween
);
String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark(targetRecord, sggNm, ownerNm);
String rmrkDtl = ComparisonRemarkBuilder.buildOwnerChangeRemarkDetail(
vhclno, inspYmd, step1OwnerName, targetChgYmd, step4OwnerName, daysBetween);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -36,20 +36,24 @@ public class OwnerLevyOver31Checker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[날짜수정후부과-명의이전] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[날짜수정후부과-명의이전] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[날짜수정후부과-명의이전] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -119,6 +123,7 @@ public class OwnerLevyOver31Checker extends AbstractComparisonChecker {
LocalDate vldPrdExpryDateMinus90 = vldPrdExpryDate.minusDays(90);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
LocalDate latestChgDate = null;
LocalDate inspDate = DateUtil.parseDate(inspYmd);
for (NewLedgerResponse.Record record : ledgerRecords) {
String chgYmd = record.getChgYmd();
@ -156,19 +161,28 @@ public class OwnerLevyOver31Checker extends AbstractComparisonChecker {
log.info("[날짜수정후부과-명의이전] 검사기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
// ========== 명의이전일자 ~ 검사일 사이의 일수 계산 ==========
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("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween);
return null;
// 명의이전일자 < 검사종료일
long daysBetween = 0;
if (latestChgDate.isBefore(inspEndDate)) {
// 검사일 - 검사종료일
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(inspEndDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
} else {
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 초과인지 확인, (검사일자 - 마지막 명의이전일자)
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
}
log.info("[날짜수정후부과-명의이전] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetChgYmd, inspYmd, daysBetween);
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
NewBasicRequest step4Request = createBasicRequest(null, vin, targetDate.format(DATE_FORMATTER));
@ -194,23 +208,29 @@ public class OwnerLevyOver31Checker extends AbstractComparisonChecker {
log.info("[날짜수정후부과-명의이전] 명의이전 전 소유자가 상품용 아님 - 소유자명: {}", step4OwnerName);
// ========== 시군구명 조회 ==========
String usgsrhldStdgCd = step1Record.getUsgsrhldStdgCd();
String sggCd = (usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5) ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);
String ownerNm = step1Record.getRprsOwnrNm();
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark(
step1Record, step4Record, targetRecord,
vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween
);
String rmrk = ComparisonRemarkBuilder.buildOwnerChangeRemark(targetRecord, sggNm, ownerNm);
String rmrkDtl = ComparisonRemarkBuilder.buildOwnerLevyOver31RemarkDetail(
vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, step1OwnerName, targetChgYmd, step4OwnerName, daysBetween);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -36,20 +36,24 @@ public class ProductCloseWithin31Checker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[내사종결-명의이전 상품용] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[내사종결-명의이전 상품용] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[내사종결-명의이전 상품용] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -115,6 +119,9 @@ public class ProductCloseWithin31Checker extends AbstractComparisonChecker {
NewLedgerResponse.Record targetRecord = null;
LocalDate latestChgDate = null;
LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd);
LocalDate vldPrdExpryDateMinus90 = vldPrdExpryDate.minusDays(90);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
LocalDate inspDate = DateUtil.parseDate(inspYmd);
for (NewLedgerResponse.Record record : ledgerRecords) {
@ -131,18 +138,16 @@ public class ProductCloseWithin31Checker extends AbstractComparisonChecker {
continue;
}
// 조건: CHG_YMD <= 검사일자
LocalDate inspEndDate = DateUtil.parseDate(inspYmd);
if (chgDate.isAfter(inspEndDate)) {
log.debug("[내사종결-명의이전 상품용] CHG_YMD > 검사종료일자 - 변경일자: {}, 검사종료일자: {}", chgYmd, inspEndYmd);
continue;
}
// 조건: 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자
if ((chgDate.isEqual(vldPrdExpryDateMinus90) || chgDate.isAfter(vldPrdExpryDateMinus90)) &&
(chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) {
// 가장 마지막 일자 찾기
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
latestChgDate = chgDate;
targetRecord = record;
log.debug("[내사종결-명의이전 상품용] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
// 가장 최근 일자 선택
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
targetRecord = record;
latestChgDate = chgDate;
log.debug("[내사종결-명의이전 상품용] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
}
}
}
@ -151,15 +156,27 @@ public class ProductCloseWithin31Checker extends AbstractComparisonChecker {
return null;
}
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 이내인지 확인
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
log.debug("[내사종결-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내가 아님 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
// 명의이전일자 < 검사종료일
long daysBetween = 0;
if (latestChgDate.isBefore(inspEndDate)) {
// 검사일 - 검사종료일
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(inspEndDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
} else {
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 초과인지 확인, (검사일자 - 마지막 명의이전일자)
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[내사종결-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
String targetChgYmd = targetRecord.getChgYmd();
log.info("[내사종결-명의이전 상품용] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
@ -191,23 +208,30 @@ public class ProductCloseWithin31Checker extends AbstractComparisonChecker {
log.info("[내사종결-명의이전 상품용] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
// ========== 시군구명 조회 ==========
String usgsrhldStdgCd = step1Record.getUsgsrhldStdgCd();
String sggCd = (usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5) ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);
String ownerNm = step1Record.getRprsOwnrNm();
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildProductCloseLevyRemark(
step1Record, step4Record, targetRecord,
vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween
);
String rmrk = ComparisonRemarkBuilder.buildProductCloseRemark(targetRecord);
String rmrkDtl = ComparisonRemarkBuilder.buildProductCloseLevyRemarkDetail(
vhclno, inspYmd, step1OwnerName, targetChgYmd, step4OwnerName, daysBetween,
vldPrdExpryYmd, inspEndYmd);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -36,20 +36,24 @@ public class ProductLevyOver31Checker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[날짜수정후부과-명의이전 상품용] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[날짜수정후부과-명의이전 상품용] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[날짜수정후부과-명의이전 상품용] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -115,6 +119,9 @@ public class ProductLevyOver31Checker extends AbstractComparisonChecker {
NewLedgerResponse.Record targetRecord = null;
LocalDate latestChgDate = null;
LocalDate vldPrdExpryDate = DateUtil.parseDate(vldPrdExpryYmd);
LocalDate vldPrdExpryDateMinus90 = vldPrdExpryDate.minusDays(90);
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
LocalDate inspDate = DateUtil.parseDate(inspYmd);
for (NewLedgerResponse.Record record : ledgerRecords) {
@ -131,18 +138,16 @@ public class ProductLevyOver31Checker extends AbstractComparisonChecker {
continue;
}
// 조건: CHG_YMD <= 검사일자
LocalDate inspEndDate = DateUtil.parseDate(inspYmd);
if (chgDate.isAfter(inspEndDate)) {
log.debug("[날짜수정후부과-명의이전 상품용] CHG_YMD > 검사종료일자 - 변경일자: {}, 검사종료일자: {}", chgYmd, inspEndYmd);
continue;
}
// 조건: 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자
if ((chgDate.isEqual(vldPrdExpryDateMinus90) || chgDate.isAfter(vldPrdExpryDateMinus90)) &&
(chgDate.isEqual(inspEndDate) || chgDate.isBefore(inspEndDate))) {
// 가장 마지막 일자 찾기
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
latestChgDate = chgDate;
targetRecord = record;
log.debug("[날짜수정후부과-명의이전 상품용] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
// 가장 최근 일자 선택
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
targetRecord = record;
latestChgDate = chgDate;
log.debug("[날짜수정후부과-명의이전 상품용] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
}
}
}
@ -151,15 +156,27 @@ public class ProductLevyOver31Checker extends AbstractComparisonChecker {
return null;
}
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 초과인지 확인
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
// 명의이전일자 < 검사종료일
long daysBetween = 0;
if (latestChgDate.isBefore(inspEndDate)) {
// 검사일 - 검사종료일
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(inspEndDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
} else {
// 조건: 가장 마지막 명의이전일자가 검사일의 기준일수 초과인지 확인, (검사일자 - 마지막 명의이전일자)
daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, inspDate);
if (daysBetween <= DAYS_THRESHOLD) {
log.debug("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 이내임 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
return null;
}
log.info("[날짜수정후부과-명의이전 상품용] 명의이전일자가 검사일의 {}일 초과 확인 - 변경일자: {}, 검사일: {}, 일수차이: {}일",
DAYS_THRESHOLD, targetRecord.getChgYmd(), inspYmd, daysBetween);
String targetChgYmd = targetRecord.getChgYmd();
log.info("[날짜수정후부과] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
@ -193,23 +210,30 @@ public class ProductLevyOver31Checker extends AbstractComparisonChecker {
log.info("[날짜수정후부과-명의이전 상품용] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
// ========== 시군구명 조회 ==========
String usgsrhldStdgCd = step1Record.getUsgsrhldStdgCd();
String sggCd = (usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5) ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);
String ownerNm = step1Record.getRprsOwnrNm();
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildProductCloseLevyRemark(
step1Record, step4Record, targetRecord,
vhclno, inspYmd, vldPrdExpryYmd, inspEndYmd, daysBetween
);
String rmrk = ComparisonRemarkBuilder.buildProductLevyRemark(targetRecord, sggNm, ownerNm);
String rmrkDtl = ComparisonRemarkBuilder.buildProductLevyOver31RemarkDetail(
vhclno, inspYmd, step1OwnerName, targetChgYmd, step4OwnerName, daysBetween,
vldPrdExpryYmd, inspEndYmd);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -36,20 +36,24 @@ public class ProductUseChecker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[상품용] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[상품용] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[상품용] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -205,22 +209,22 @@ public class ProductUseChecker extends AbstractComparisonChecker {
log.info("[상품용] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildProductUseRemark(
step1Record, step4Record, targetRecord,
vldPrdExpryYmd, inspEndYmd
);
String rmrk = ComparisonRemarkBuilder.buildProductUseRemark();
String rmrkDtl = ComparisonRemarkBuilder.buildProductUseRemarkDetail(
vhclno, inspYmd, step1OwnerName, targetChgYmd, step4OwnerName);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -36,20 +36,24 @@ public class ProductUseChnageChecker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
String inspYmd = existingData.getInspYmd();
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd();
String inspEndYmd = existingData.getInspEndYmd();
String inspYmd = existingData.getInspYmd().replace("-", "");
String vldPrdExpryYmd = existingData.getVldPrdExpryYmd().replace("-", "");
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
try {
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사일) ==========
log.info("[상품용-변경등록] Step 1: 자동차기본정보 조회 - 차량번호: {}, 검사일: {}", vhclno, inspYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, inspYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtId());
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[상품용-변경등록] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// Step 1: 공통으로 이미 호출됨 - step1Response 사용
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
log.warn("[상품용-변경등록] Step 1 응답 없음 - 차량번호: {}", vhclno);
return null;
@ -210,22 +214,22 @@ public class ProductUseChnageChecker extends AbstractComparisonChecker {
log.info("[상품용-변경등록] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
// ========== 비고 생성 ==========
String rmrk = ComparisonRemarkBuilder.buildProductUseChangeRemark(
step1Record, step4Record, targetRecord,
vldPrdExpryYmd, inspEndYmd
);
String rmrk = ComparisonRemarkBuilder.buildProductUseRemark();
String rmrkDtl = ComparisonRemarkBuilder.buildProductUseRemarkDetail(
vhclno, inspYmd, step1OwnerName, targetChgYmd, step4OwnerName);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -3,7 +3,6 @@ package go.kr.project.carInspectionPenalty.registration.service.impl.delay_check
import egovframework.constant.TaskPrcsSttsConstants;
import egovframework.exception.MessageException;
import egovframework.util.DateUtil;
import egovframework.util.SessionUtil;
import go.kr.project.api.model.request.NewBasicRequest;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.api.service.ExternalVehicleApiService;
@ -12,7 +11,6 @@ import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.impl.ComparisonRemarkBuilder;
import go.kr.project.login.model.LoginUserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -35,7 +33,7 @@ public class TransferCase115DayChecker extends AbstractComparisonChecker {
}
@Override
public String check(CarFfnlgTrgtVO existingData) {
public String check(CarFfnlgTrgtVO existingData, String userOrgCd, NewBasicResponse step0Response, NewBasicResponse step1Response) {
String vhclno = existingData.getVhclno();
try {
@ -55,7 +53,7 @@ public class TransferCase115DayChecker extends AbstractComparisonChecker {
if (daycnt > 115) {
// 이첩-2: 부과기준일 = 검사종료일자 + 115일
String inspEndYmd = existingData.getInspEndYmd();
String inspEndYmd = existingData.getInspEndYmd().replace("-", "");
LocalDate inspEndDate = DateUtil.parseDate(inspEndYmd);
LocalDate levyCrtrDate = inspEndDate.plusDays(115);
levyCrtrYmd = levyCrtrDate.format(DATE_FORMATTER);
@ -63,15 +61,24 @@ public class TransferCase115DayChecker extends AbstractComparisonChecker {
log.info("[이첩-2] 부과기준일 = 검사종료일자({}) + 115일 = {}", inspEndYmd, levyCrtrYmd);
} else {
// 이첩-1: 부과기준일 = 검사일자
levyCrtrYmd = existingData.getInspYmd();
levyCrtrYmd = existingData.getInspYmd().replace("-", "");
transferType = "이첩-1";
log.info("[이첩-1] 부과기준일 = 검사일자 = {}", levyCrtrYmd);
}
// Step 0: 공통으로 이미 호출됨 - step0Response 사용
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[이첩] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// 자동차기본정보 API 호출 (부과기준일 기준)
log.info("[{}] 자동차기본정보 조회 - 차량번호: {}, 부과기준일: {}", transferType, vhclno, levyCrtrYmd);
log.info("[{}] 자동차기본정보 조회 - 차대번호: {}, 부과기준일: {}", transferType, step0vin, levyCrtrYmd);
NewBasicRequest request = createBasicRequest(vhclno, null, levyCrtrYmd);
NewBasicRequest request = createBasicRequest(null, step0vin, levyCrtrYmd);
NewBasicResponse response = apiService.getBasicInfo(request);
// API 응답에 CAR_FFNLG_TRGT_ID 업데이트
@ -93,16 +100,14 @@ public class TransferCase115DayChecker extends AbstractComparisonChecker {
return null;
}
// 세션에서 사용자 정보 조회
LoginUserVO userInfo = SessionUtil.getLoginUser();
if (userInfo == null || userInfo.getOrgCd() == null) {
// 사용자 조직코드 유효성 검사
if (userOrgCd == null) {
log.debug("[{}] 사용자 정보 없음", transferType);
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)) {
@ -122,22 +127,31 @@ public class TransferCase115DayChecker extends AbstractComparisonChecker {
sggNm = "";
}
// 소유자명
String ownerNm = record.getRprsOwnrNm();
// 비고 생성
String rmrk;
String rmrkDtl;
if ("이첩-1".equals(transferType)) { // 5번
rmrk = ComparisonRemarkBuilder.buildTransferCase1Remark(sggNm, userOrg4);
rmrk = ComparisonRemarkBuilder.buildTransferCase1Remark(sggNm, ownerNm);
rmrkDtl = ComparisonRemarkBuilder.buildTransferCase1RemarkDetail(
vhclno, existingData.getInspYmd(), ownerNm, usgsrhldStdgCd, sggNm, userOrgCd);
} else { // 7번
rmrk = ComparisonRemarkBuilder.buildTransferCase2Remark(sggNm, legalDong4);
rmrk = ComparisonRemarkBuilder.buildTransferCase2Remark(sggNm, ownerNm);
rmrkDtl = ComparisonRemarkBuilder.buildTransferCase2RemarkDetail(
vhclno, existingData.getInspEndYmd(), ownerNm, usgsrhldStdgCd, sggNm, userOrgCd, daycntStr);
}
// DB 업데이트
existingData.setCarBassMatterInqireId(response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm());
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {

@ -108,7 +108,7 @@ public class CarFfnlgTrgtIncmpController {
paramVO.setTotalCount(totalCount);
// 3. 페이징 활성화
paramVO.setPagingYn("Y");
//paramVO.setPagingYn("Y");
// 목록 조회
List<CarFfnlgTrgtIncmpVO> list = service.selectList(paramVO);

@ -74,6 +74,10 @@ public class CarFfnlgTrgtIncmpExcelVO {
@ExcelColumn(headerName = "비고", headerWidth = 30, align = ExcelColumn.Align.LEFT)
private String rmrk;
/** 비고 상세 */
@ExcelColumn(headerName = "비고 상세", headerWidth = 50, align = ExcelColumn.Align.LEFT)
private String rmrkDtl;
/** 기본사항조회성명 */
@ExcelColumn(headerName = "기본사항조회성명", headerWidth = 18, align = ExcelColumn.Align.CENTER)
private String carBscMttrInqFlnm;

@ -42,7 +42,8 @@ public class CarFfnlgTrgtIncmpVO extends PagingVO {
private String taskPrcsSttsCd; // 업무 처리 상태 코드 (01=접수, 02=처리중, 03=완료)
private String taskPrcsYmd; // 업무 처리 일자
private String rmrk; // 비고
private String rmrkDtl; // 비고 상세
// API 연동 필드
private String carBassMatterInqireId; // 자동차 기본 사항 조회 ID
private String carLedgerFrmbkId; // 자동차 등록 원부 갑 ID

@ -24,7 +24,8 @@ public interface ComparisonOmService {
* <p> levyCrtrYmd() .</p>
*
* @param existingData (levyCrtrYmd )
* @param userInfo
* @return (02=, 03=, null=)
*/
String executeComparison(CarFfnlgTrgtIncmpVO existingData);
String executeComparison(CarFfnlgTrgtIncmpVO existingData, go.kr.project.login.model.LoginUserVO userInfo);
}

@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
@ -26,6 +27,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Service
@ -38,8 +44,10 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
private final CarFfnlgTrgtIncmpMapper mapper;
private final CarFfnlgPrnParseConfig parseConfig;
private final ComparisonOmService comparisonOmService;
private final TransactionTemplate transactionTemplate;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter DATE_FORMATTER_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public int selectListTotalCount(CarFfnlgTrgtIncmpVO vo) {
@ -223,8 +231,8 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
// 업무 처리 상태 및 등록자 설정
vo.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT);
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER));
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
vo.setRgtr(rgtr);
// DB 저장
@ -396,127 +404,211 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
}
/**
* API /
* API / ()
* = + OM_DAY_CD D (146)
* : , .
* , .
*
* @param targetList
* @return
*/
@Override
@Transactional
public Map<String, Object> compareWithApi(List<Map<String, String>> targetList) {
log.info("========== 미필 API 호출 및 비교 시작 ==========");
log.info("========== 미필 API 호출 및 비교 시작 (병렬처리) ==========");
log.info("선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);
if (targetList == null || targetList.isEmpty()) {
throw new IllegalArgumentException("선택된 데이터가 없습니다.");
}
List<Map<String, Object>> compareResults = new ArrayList<>();
int successCount = 0;
int productUseCount = 0;
int transferCount = 0;
int normalCount = 0;
// 가산일 조회 (OM_DAY_CD 코드의 D값)
// 가산일 조회 (OM_DAY_CD 코드의 D값) - 병렬처리 전에 미리 조회
String plusDayStr = mapper.selectOmDayPlusDay();
int plusDay = 146; // 기본값
final int plusDay;
if (plusDayStr != null && !plusDayStr.isEmpty()) {
int parsed = 146;
try {
plusDay = Integer.parseInt(plusDayStr);
parsed = Integer.parseInt(plusDayStr);
} catch (NumberFormatException e) {
log.warn("가산일 파싱 실패, 기본값 146 사용: {}", plusDayStr);
}
plusDay = parsed;
} else {
plusDay = 146; // 기본값
}
log.info("부과일자 가산일: {}일", plusDay);
for (Map<String, String> target : targetList) {
String carFfnlgTrgtIncmpId = target.get("carFfnlgTrgtIncmpId");
String vhclno = target.get("vhclno");
String inspVldPrd = target.get("inspVldPrd");
log.info("처리 중 - 차량번호: {}, 검사유효기간: {}", vhclno, inspVldPrd);
Map<String, Object> compareResult = new HashMap<>();
compareResult.put("carFfnlgTrgtIncmpId", carFfnlgTrgtIncmpId);
compareResult.put("vhclno", vhclno);
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtIncmpVO existingData = new CarFfnlgTrgtIncmpVO();
existingData.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
existingData = mapper.selectOne(existingData);
if (existingData == null) {
String errorMsg = String.format("기존 데이터를 찾을 수 없습니다. 차량번호: %s", vhclno);
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가
if (!TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT.equals(existingData.getTaskPrcsSttsCd())) {
String errorMsg = String.format("접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s",
vhclno, existingData.getTaskPrcsSttsCd());
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 세션에서 사용자 정보 조회 (병렬처리 전에 메인 스레드에서 조회)
go.kr.project.login.model.LoginUserVO userInfo = null;
try {
userInfo = egovframework.util.SessionUtil.getLoginUser();
} catch (Exception e) {
log.warn("세션에서 사용자 정보 조회 실패", e);
}
final go.kr.project.login.model.LoginUserVO finalUserInfo = userInfo;
// 3. 검사유효기간에서 부과일자 계산 (종료일 + 가산일)
String levyCrtrYmd = calculateLevyCrtrYmdFromInspVldPrd(inspVldPrd, plusDay);
existingData.setLevyCrtrYmd(levyCrtrYmd);
log.info("부과일자 계산 완료 - 검사유효기간: {}, 부과일자: {}", inspVldPrd, levyCrtrYmd);
// I/O 작업이므로 CPU 코어 수의 2배로 스레드 풀 생성
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
// 4. 비교 로직 실행
String statusCode = comparisonOmService.executeComparison(existingData);
log.info("병렬처리 스레드 풀 크기: {}", threadPoolSize);
// 결과 처리
if (statusCode != null) {
if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE.equals(statusCode)) {
try {
// 병렬로 각 건 처리
List<CompletableFuture<Map<String, Object>>> futures = targetList.stream()
.map(target -> CompletableFuture.supplyAsync(() -> processOneTarget(target, plusDay, finalUserInfo), executor))
.collect(Collectors.toList());
// 모든 작업 완료 대기
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 결과 수집
List<Map<String, Object>> compareResults = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 통계 집계
int successCount = 0;
int failCount = 0;
int productUseCount = 0;
int transferCount = 0;
int normalCount = 0;
for (Map<String, Object> result : compareResults) {
Boolean success = (Boolean) result.get("success");
if (Boolean.TRUE.equals(success)) {
successCount++;
String processStatus = (String) result.get("processStatus");
if ("상품용".equals(processStatus)) {
productUseCount++;
compareResult.put("processStatus", "상품용");
compareResult.put("message", "상품용으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER.equals(statusCode)) {
} else if ("이첩".equals(processStatus)) {
transferCount++;
compareResult.put("processStatus", "이첩");
compareResult.put("message", "이첩으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED.equals(statusCode)) {
} else if ("내사종결".equals(processStatus) || "정상".equals(processStatus)) {
normalCount++;
compareResult.put("processStatus", "내사종결");
compareResult.put("message", "내사종결로 처리되었습니다.");
} else {
normalCount++;
compareResult.put("processStatus", "기타");
compareResult.put("message", "기타 상태로 처리되었습니다.");
}
compareResult.put("success", true);
successCount++;
} else {
normalCount++;
compareResult.put("success", true);
compareResult.put("message", "정상 처리되었습니다.");
compareResult.put("processStatus", "정상");
successCount++;
failCount++;
}
} catch (Exception e) {
log.error("데이터 비교 중 오류 발생 - 차량번호: {}, 전체 롤백 처리", vhclno, e);
throw new MessageException(e.getMessage(), e);
}
compareResults.add(compareResult);
Map<String, Object> resultData = new HashMap<>();
resultData.put("compareResults", compareResults);
resultData.put("totalCount", targetList.size());
resultData.put("successCount", successCount);
resultData.put("failCount", failCount);
resultData.put("productUseCount", productUseCount);
resultData.put("transferCount", transferCount);
resultData.put("normalCount", normalCount);
log.info("========== 미필 API 호출 및 비교 완료 (병렬처리) ==========");
log.info("전체: {}건, 성공: {}건, 실패: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건",
targetList.size(), successCount, failCount, productUseCount, transferCount, normalCount);
return resultData;
} finally {
// ExecutorService 종료
executor.shutdown();
try {
if (!executor.awaitTermination(120, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* ( )
*
* @param target
* @param plusDay
* @return
*/
private Map<String, Object> processOneTarget(Map<String, String> target, int plusDay, go.kr.project.login.model.LoginUserVO userInfo) {
String carFfnlgTrgtIncmpId = target.get("carFfnlgTrgtIncmpId");
String vhclno = target.get("vhclno");
String inspVldPrd = target.get("inspVldPrd");
Map<String, Object> resultData = new HashMap<>();
resultData.put("compareResults", compareResults);
resultData.put("totalCount", targetList.size());
resultData.put("successCount", successCount);
resultData.put("failCount", 0);
resultData.put("productUseCount", productUseCount);
resultData.put("transferCount", transferCount);
resultData.put("normalCount", normalCount);
log.info("처리 중 - 차량번호: {}, 검사유효기간: {}", vhclno, inspVldPrd);
log.info("========== 미필 API 호출 및 비교 완료 ==========");
log.info("성공: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건",
successCount, productUseCount, transferCount, normalCount);
Map<String, Object> compareResult = new HashMap<>();
compareResult.put("carFfnlgTrgtIncmpId", carFfnlgTrgtIncmpId);
compareResult.put("vhclno", vhclno);
try {
// 개별 트랜잭션으로 실행
String statusCode = transactionTemplate.execute(status -> {
try {
// 1. 기존 데이터 조회
CarFfnlgTrgtIncmpVO existingData = new CarFfnlgTrgtIncmpVO();
existingData.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
existingData = mapper.selectOne(existingData);
if (existingData == null) {
String errorMsg = String.format("기존 데이터를 찾을 수 없습니다. 차량번호: %s", vhclno);
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가
if (!TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT.equals(existingData.getTaskPrcsSttsCd())) {
String errorMsg = String.format("접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s",
vhclno, existingData.getTaskPrcsSttsCd());
log.error(errorMsg);
throw new MessageException(errorMsg);
}
// 3. 검사유효기간에서 부과일자 계산 (종료일 + 가산일)
String levyCrtrYmd = calculateLevyCrtrYmdFromInspVldPrd(inspVldPrd, plusDay);
existingData.setLevyCrtrYmd(levyCrtrYmd);
log.info("부과일자 계산 완료 - 검사유효기간: {}, 부과일자: {}", inspVldPrd, levyCrtrYmd);
// 4. 비교 로직 실행
return comparisonOmService.executeComparison(existingData, userInfo);
} catch (Exception e) {
// 트랜잭션 롤백
status.setRollbackOnly();
throw e;
}
});
// 결과 처리
if (statusCode != null) {
// 비교 규칙이 적용됨
if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE.equals(statusCode)) {
compareResult.put("processStatus", "상품용");
compareResult.put("message", "상품용으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER.equals(statusCode)) {
compareResult.put("processStatus", "이첩");
compareResult.put("message", "이첩으로 처리되었습니다.");
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED.equals(statusCode)) {
compareResult.put("processStatus", "내사종결");
compareResult.put("message", "내사종결로 처리되었습니다.");
} else {
compareResult.put("processStatus", "기타");
compareResult.put("message", "기타 상태로 처리되었습니다.");
}
compareResult.put("success", true);
} else {
// 정상 처리 (비교 로직에 해당 안됨)
compareResult.put("success", true);
compareResult.put("message", "정상 처리되었습니다.");
compareResult.put("processStatus", "정상");
}
} catch (Exception e) {
log.error("데이터 비교 중 오류 발생 - 차량번호: {}", vhclno, e);
compareResult.put("success", false);
compareResult.put("message", "처리 실패: " + e.getMessage());
compareResult.put("processStatus", "실패");
}
return resultData;
return compareResult;
}
/**
@ -562,7 +654,7 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
List<CarFfnlgTrgtIncmpVO> updatedRows = modifyData.getUpdatedRows();
if (updatedRows != null && !updatedRows.isEmpty()) {
for (CarFfnlgTrgtIncmpVO vo : updatedRows) {
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
result += mapper.updateTaskPrcsSttsCdAndRmrk(vo);
}
}
@ -789,10 +881,8 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
errors.add(String.format("[데이터 %d] 차량번호가 너무 깁니다. 차량번호: %s (최대 30자)", dataLineNumber, vo.getVhclno()));
}
// 2. 소유자명 검증
if (vo.getOwnrNm() == null || vo.getOwnrNm().isEmpty()) {
errors.add(String.format("[데이터 %d] 소유자명이 누락되었습니다. 차량번호: %s", dataLineNumber, vhclno));
} else if (vo.getOwnrNm().length() > 75) {
// 2. 소유자명 검증 (소유자명에 null or 공백 들어올수 있음 - 필수 아님)
if (vo.getOwnrNm() != null && !vo.getOwnrNm().isEmpty() && vo.getOwnrNm().length() > 75) {
errors.add(String.format("[데이터 %d] 소유자명이 너무 깁니다. 소유자명: %s (최대 75자), 차량번호: %s",
dataLineNumber, vo.getOwnrNm(), vhclno));
}

@ -2,7 +2,6 @@ package go.kr.project.carInspectionPenalty.registrationOm.service.impl;
import egovframework.util.DateUtil;
import egovframework.util.StringUtil;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.api.model.response.NewLedgerResponse;
/**
@ -10,234 +9,329 @@ import go.kr.project.api.model.response.NewLedgerResponse;
*
* <p> .</p>
* <p> = + 146</p>
*
* <p> :</p>
* <ul>
* <li>1. : </li>
* <li>2. : (25.5.19.) , / </li>
* <li>3. : (25.5.19.)()</li>
* <li>4. : / , 115 </li>
* </ul>
*/
public class ComparisonOmRemarkBuilder {
/**
* -
* :
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( )
* @param ledgerRecord
* @param inspVldPrdEnd
* @param levyCrtrYmd ( + 146)
* @return
*/
public static String buildProductUseRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String inspVldPrdEnd,
String levyCrtrYmd) {
StringBuilder sb = new StringBuilder();
sb.append("상품용 - 미필검사\n");
// 1. 부과일자 기준 소유자 정보
sb.append("\n■ 부과일자 기준 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
// 2. 명의이전 시점 소유자 정보
sb.append("\n■ 명의이전 시점 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
// 3. 갑부 상세 정보 (명의이전 이력)
sb.append("\n■ 갑부 상세 (명의이전 이력)\n");
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
// 4. 검사유효기간 정보
sb.append("\n■ 검사유효기간 정보\n");
sb.append(" - 검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
return sb.toString();
public static String buildProductUseRemark() {
return "상품용";
}
/**
* - -
* -
* : (25.9.5.)
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( )
* @param ledgerRecord ( )
* @param inspVldPrdEnd
* @param levyCrtrYmd ( + 146)
* @param ledgerRecord ( )
* @return
*/
public static String buildProductUseChangeRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String inspVldPrdEnd,
String levyCrtrYmd) {
StringBuilder sb = new StringBuilder();
sb.append("상품용 - 변경등록 - 미필\n");
// 1. 부과일자 기준 소유자 정보
sb.append("\n■ 부과일자 기준 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
// 2. 변경등록 시점 소유자 정보
sb.append("\n■ 변경등록 시점 소유자정보\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
// 3. 갑부 상세 정보 (변경등록 이력)
sb.append("\n■ 갑부 상세 (변경등록 이력)\n");
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
sb.append(" - 변경업무명: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeNm())).append("\n");
sb.append(" - 특별사항: ").append(StringUtil.nvl(ledgerRecord.getSpcablMttr())).append("\n");
return sb.toString();
public static String buildProductCloseLevyRemark(NewLedgerResponse.Record ledgerRecord) {
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
return String.format("명의이전(%s) 이전소유자 상품용", chgYmdFormatted);
}
/**
* - ( , )
*
* :
* (25.9.3.)
* 222283
* -
*
*
*
* -
* : / , (25.5.19.)()
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( = )
* @param ledgerRecord ( )
* @param sggNm
* @param ownerNm
* @param vhclno
* @param levyCrtrYmd ( + 146)
* @param inspVldPrdStart
* @param inspVldPrdEnd
* @param daysBetween
* @return
*/
public static String buildProductCloseLevyRemark(
NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vhclno,
String levyCrtrYmd,
String inspVldPrdStart,
String inspVldPrdEnd,
long daysBetween) {
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
public static String buildOwnerChangeRemark(NewLedgerResponse.Record ledgerRecord,
String sggNm, String ownerNm, String vhclno) {
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
StringBuilder sb = new StringBuilder();
// 첫 줄: 명의이전(25.9.3.) 이전소유자 상품용
sb.append("명의이전(").append(chgYmdFormatted).append(") 이전소유자 상품용").append("\n");
// 둘째 줄: 차량번호
sb.append(StringUtil.nvl(vhclno)).append("\n");
// 셋째 줄: 검사유효기간 시작일 - 종료일
sb.append(" - 검사유효기간: ").append(DateUtil.formatDateString(inspVldPrdStart))
.append(" - ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
// 넷째 줄: 부과일자 일자
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
return String.format("%s/ %s, 미수검명의이전(%s)(%s)",
StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm), chgYmdFormatted, StringUtil.nvl(vhclno));
}
// 다섯째 줄: 명의이전 일자
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
/**
* -
* : / , 115
*
* @param sggNm
* @param ownerNm
* @return
*/
public static String buildTransferRemark(String sggNm, String ownerNm) {
return String.format("%s/ %s, 115일 도래지", StringUtil.nvl(sggNm), StringUtil.nvl(ownerNm));
}
// 여섯째 줄: 상품용 일자 (명의이전 일자와 동일)
sb.append(" - 상품용: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
// ========================== 비고 상세 (RMRK_DTL) 생성 메서드 ==========================
// 일곱째 줄: 일수차이, 미필은 일수차이 제외
//sb.append("일수차이: ").append(daysBetween).append("일");
/**
* -
*
* @param vhclno
* @param inspVldPrdEnd
* @param levyCrtrYmd (+146)
* @param step1OwnerNm Step1 ( )
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @return
*/
public static String buildProductUseRemarkDetail(String vhclno, String inspVldPrdEnd,
String levyCrtrYmd, String step1OwnerNm,
String targetChgYmd, String step4OwnerNm) {
StringBuilder sb = new StringBuilder();
sb.append("[상품용 판정 - 미필]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
sb.append("부과기준일자 계산: 검사유효기간 종료일 + 146일 = ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
sb.append("검사유효기간 종료일 + 31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과일자(").append(DateUtil.formatDateString(levyCrtrYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → O\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 변경일자(CHG_YMD) ≤ 검사유효기간 종료일+31일 (").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 변경일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 갑부 변경일자(").append(DateUtil.formatDateString(targetChgYmd)).append(")\n");
sb.append(" - 변경일 기준 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step4 대표소유자 회원번호 → 일치\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 부과일자(검사유효기간 종료일+146일) 기준 소유자명에 '상품용' 포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사유효기간 종료일+31일 이내에 존재\n");
sb.append("3. 갑부 변경일자 시점의 대표소유자 회원번호와 부과일자 기준 대표소유자 회원번호가 동일\n");
sb.append("→ 상품용 판정 (처리상태: 02_상품용)\n");
return sb.toString();
}
/**
* - ( )
*
* :
* (25.9.3.)
* 222283
* -
*
*
* -
*
* @param step1Record Step 1 API ( )
* @param step4Record Step 4 API ( )
* @param ledgerRecord ( )
* @param vhclno
* @param levyCrtrYmd ( + 146)
* @param inspVldPrdStart
* @param inspVldPrdEnd
* @param daysBetween
* @return
* @param levyCrtrYmd (+146)
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @return
*/
public static String buildOwnerChangeRemark(NewBasicResponse.Record step1Record,
NewBasicResponse.Record step4Record,
NewLedgerResponse.Record ledgerRecord,
String vhclno,
String levyCrtrYmd,
String inspVldPrdStart,
String inspVldPrdEnd,
long daysBetween) {
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
public static String buildProductCloseLevyRemarkDetail(String vhclno, String inspVldPrdEnd,
String levyCrtrYmd, String step1OwnerNm,
String targetChgYmd, String step4OwnerNm) {
StringBuilder sb = new StringBuilder();
sb.append("[명의이전 이전소유자 상품용 판정 - 미필]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
sb.append("부과기준일자 계산: 검사유효기간 종료일 + 146일 = ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
sb.append("검사유효기간 종료일 + 31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과일자(").append(DateUtil.formatDateString(levyCrtrYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 변경일자(CHG_YMD) > 검사유효기간 종료일+31일 (").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자-1일(").append(DateUtil.formatDateString(
DateUtil.parseDate(targetChgYmd).minusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 명의이전 전 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: 명의이전 전 소유자명에 '상품용' 포함 → O\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 부과일자(검사유효기간 종료일+146일) 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사유효기간 종료일+31일 이후에 존재\n");
sb.append("3. 명의이전일자-1일 시점의 소유자명에 '상품용' 포함\n");
sb.append("→ 명의이전 이전소유자 상품용 판정 (처리상태: 01_접수)\n");
// 첫 줄: 명의이전(25.9.3.)
sb.append("명의이전(").append(chgYmdFormatted).append(")").append("\n");
// 둘째 줄: 차량번호
sb.append(StringUtil.nvl(vhclno)).append("\n");
// 셋째 줄: 검사유효기간 시작일 - 종료일
sb.append(" - 검사유효기간: ").append(DateUtil.formatDateString(inspVldPrdStart))
.append(" - ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
// 넷째 줄: 부과일자 일자
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
// 다섯째 줄: 명의이전 일자
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
return sb.toString();
}
// 일곱째 줄: 일수차이, 미필은 일수차이 제외
// sb.append("일수차이: ").append(daysBetween).append("일");
/**
* -
*
* @param vhclno
* @param inspVldPrdEnd
* @param levyCrtrYmd (+146)
* @param step1OwnerNm Step1
* @param targetChgYmd
* @param step4OwnerNm Step4 ( )
* @return
*/
public static String buildOwnerChangeRemarkDetail(String vhclno, String inspVldPrdEnd,
String levyCrtrYmd, String step1OwnerNm,
String targetChgYmd, String step4OwnerNm) {
StringBuilder sb = new StringBuilder();
sb.append("[미수검명의이전 판정 - 미필]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
sb.append("부과기준일자 계산: 검사유효기간 종료일 + 146일 = ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
sb.append("검사유효기간 종료일 + 31일 기준일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과일자(").append(DateUtil.formatDateString(levyCrtrYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1OwnerNm)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 목적: 차량번호, 성명, 주민번호, 법정동코드 조회\n\n");
sb.append("Step3) 자동차등록원부(갑) 조회\n");
sb.append(" - 조회 기준: 차량번호 + 성명 + 주민번호 + 법정동코드\n");
sb.append(" - 검색 조건:\n");
sb.append(" · 변경업무구분코드(CHG_TASK_SE_CD) = '11' (명의이전)\n");
sb.append(" · 변경일자(CHG_YMD) > 검사유효기간 종료일+31일 (").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).plusDays(31).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" · 조건 충족하는 레코드 중 가장 마지막 일자 선택\n");
sb.append(" - 선택된 갑부 명의이전 일자: ").append(DateUtil.formatDateString(targetChgYmd)).append("\n\n");
sb.append("Step4) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 명의이전일자-1일(").append(DateUtil.formatDateString(
DateUtil.parseDate(targetChgYmd).minusDays(1).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 명의이전 전 소유자명: ").append(StringUtil.nvl(step4OwnerNm)).append("\n");
sb.append(" - 조건 확인: 부과일자 기준 소유자 = 명의이전 전 소유자 → 동일\n");
sb.append(" - 명의이전 전 소유자명에 '상품용' 포함 → X\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 부과일자(검사유효기간 종료일+146일) 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 갑부 상세에서 변경업무구분코드 '11'(명의이전) 레코드가 검사유효기간 종료일+31일 이후에 존재\n");
sb.append("3. 명의이전 전 소유자명과 부과일자 기준 소유자명이 동일 (미수검 상태에서 명의이전 발생)\n");
sb.append("4. 명의이전 전 소유자명에 '상품용' 미포함\n");
sb.append("→ 미수검명의이전 판정 (처리상태: 01_접수)\n");
return sb.toString();
}
/**
* - Case -1 ( )
* -
*
* @param vhclno
* @param inspVldPrdEnd
* @param levyCrtrYmd (+146)
* @param ownerNm
* @param usgsrhldStdgCd
* @param sggNm
* @param userOrg4 4
* @return
* @param userOrgCd
* @return
*/
public static String buildTransferCase1Remark(String sggNm, String userOrg4) {
return String.format("%s, 부과일자사용본거지, [사용자 조직코드 앞 4자리: %s, 법정동명: %s]",
sggNm, userOrg4, sggNm);
public static String buildTransferRemarkDetail(String vhclno, String inspVldPrdEnd,
String levyCrtrYmd, String ownerNm,
String usgsrhldStdgCd, String sggNm, String userOrgCd) {
StringBuilder sb = new StringBuilder();
sb.append("[이첩 판정 - 미필]\n\n");
sb.append("=== 기본 정보 ===\n");
sb.append("차량번호: ").append(StringUtil.nvl(vhclno)).append("\n");
sb.append("검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
sb.append("부과기준일자 계산: 검사유효기간 종료일 + 146일 = ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
sb.append("검사유효기간 종료일 - 90일: ").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append("\n\n");
sb.append("=== API 호출 및 비교 과정 ===\n");
sb.append("Step1) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차량번호 + 부과일자(").append(DateUtil.formatDateString(levyCrtrYmd)).append(")\n");
sb.append(" - 소유자명: ").append(StringUtil.nvl(ownerNm)).append("\n");
sb.append(" - 사용본거지법정동코드: ").append(StringUtil.nvl(usgsrhldStdgCd)).append("\n");
sb.append(" - 조건 확인: 소유자명에 '상품용' 포함 여부 → X\n\n");
sb.append("Step2) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 검사유효기간 종료일-90일(").append(DateUtil.formatDateString(
DateUtil.parseDate(inspVldPrdEnd).minusDays(90).format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"))
)).append(")\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step2 대표소유자 회원번호 → 동일\n\n");
sb.append("Step3) 자동차기본정보 조회\n");
sb.append(" - 조회 기준: 차대번호 + 오늘일자\n");
sb.append(" - 조건 확인: Step1 대표소유자 회원번호 = Step3 대표소유자 회원번호 → 동일\n\n");
sb.append("=== 법정동코드 비교 ===\n");
String legalDong4 = usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 4 ? usgsrhldStdgCd.substring(0, 4) : "";
String userOrg4 = userOrgCd != null && userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : "";
sb.append("사용본거지법정동코드 앞 4자리: ").append(legalDong4).append("\n");
sb.append("현재 사용자 조직코드 앞 4자리: ").append(userOrg4).append("\n");
sb.append("코드 불일치 → 이첩 대상\n");
sb.append("시군구코드: ").append(usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : "").append("\n");
sb.append("시군구명: ").append(StringUtil.nvl(sggNm)).append("\n\n");
sb.append("=== 판정 근거 ===\n");
sb.append("1. 부과일자(검사유효기간 종료일+146일) 기준 소유자명에 '상품용' 미포함\n");
sb.append("2. 부과일자/검사유효기간 종료일-90일/오늘일자 세 시점의 대표소유자 회원번호 모두 동일 (소유자 변동 없음)\n");
sb.append("3. 사용본거지법정동코드 앞 4자리와 현재 사용자 조직코드 앞 4자리가 불일치\n");
sb.append("→ 이첩 판정 (처리상태: 03_이첩, 115일 도래지)\n");
return sb.toString();
}
/**
* - Case -2 (146 )
* ( 3 1300)
*
* @param sggNm
* @param legalDong4 4
* @return
* @param str
* @return
*/
public static String buildTransferCase2Remark(String sggNm, String legalDong4) {
return String.format("%s, 115일 도래지, [법정동코드: %s, 법정동명: %s]",
sggNm, legalDong4, sggNm);
private static String truncateToMaxLength(String str) {
if (str == null) {
return "";
}
// 4000바이트 / 3(한글) = 약 1333자, 여유있게 1300자로 제한
if (str.length() > 1300) {
return str.substring(0, 1297) + "...";
}
return str;
}
/**
@ -253,21 +347,14 @@ public class ComparisonOmRemarkBuilder {
StringBuilder detail = new StringBuilder();
// 변경 정보
StringUtil.appendIfNotEmpty(detail, "변경업무구분코드", record.getChgTaskSeCd());
StringUtil.appendIfNotEmpty(detail, "변경업무구분명", record.getChgTaskSeNm());
StringUtil.appendIfNotEmpty(detail, "변경일자", DateUtil.formatDateString(record.getChgYmd()));
// 주요 정보
StringUtil.appendIfNotEmpty(detail, "주요번호", record.getMainNo());
StringUtil.appendIfNotEmpty(detail, "일련번호", record.getSno());
StringUtil.appendIfNotEmpty(detail, "특별사항", record.getSpcablMttr());
// 명의자 정보
StringUtil.appendIfNotEmpty(detail, "명의자명", record.getHshldrNm());
StringUtil.appendIfNotEmpty(detail, "명의자식별번호", StringUtil.maskIdecno(record.getHshldrIdecno()));
// 기타
StringUtil.appendIfNotEmpty(detail, "신청접수번호", record.getAplyRcptNo());
StringUtil.appendIfNotEmpty(detail, "차량관리번호", record.getVhmno());
StringUtil.appendIfNotEmpty(detail, "원부그룹번호", record.getLedgerGroupNo());

@ -1,8 +1,10 @@
package go.kr.project.carInspectionPenalty.registrationOm.service.impl;
import egovframework.util.SessionUtil;
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
import go.kr.project.carInspectionPenalty.registrationOm.service.ComparisonOmService;
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker.*;
import go.kr.project.login.model.LoginUserVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
@ -30,32 +32,35 @@ public class ComparisonOmServiceImpl extends EgovAbstractServiceImpl implements
* <p> (levyCrtrYmd) existingData .</p>
*/
@Override
public String executeComparison(CarFfnlgTrgtIncmpVO existingData) {
public String executeComparison(CarFfnlgTrgtIncmpVO existingData, LoginUserVO userInfo) {
String vhclno = existingData.getVhclno();
String levyCrtrYmd = existingData.getLevyCrtrYmd();
log.info("========== 미필 비교 로직 시작: {}, 부과일자: {} ==========", vhclno, levyCrtrYmd);
// 사용자 조직코드 추출
String userOrgCd = userInfo != null ? userInfo.getOrgCd() : null;
// ========== 1. 상품용 체크 - api-1번호출.소유자명.contains("상품용") ==========
String productUseResult = productUseOmChecker.check(existingData);
String productUseResult = productUseOmChecker.check(existingData, userOrgCd);
if (productUseResult != null) {
log.info("========== 미필 비교 로직 종료 (상품용): {} ==========", vhclno);
return productUseResult;
}
// ========== 2. 명의이전 소유자 확인 (검사유효기간 종료일 이후 명의이전) ==========
String ownerTransferResult = ownerTransferOmChecker.check(existingData);
if (ownerTransferResult != null) {
log.info("========== 미필 비교 로직 종료 (명의이전 소유자 확인): {} ==========", vhclno);
return ownerTransferResult;
}
// ========== 3. 이첩 체크 ==========
String transferResult = transferOmChecker.check(existingData);
// ========== 2. 이첩 체크 ==========
String transferResult = transferOmChecker.check(existingData, userOrgCd);
if (transferResult != null) {
log.info("========== 미필 비교 로직 종료 (이첩): {} ==========", vhclno);
return transferResult;
}
// ========== 3. 명의이전 소유자 확인 (검사유효기간 종료일 이후 명의이전) ==========
String ownerTransferResult = ownerTransferOmChecker.check(existingData, userOrgCd);
if (ownerTransferResult != null) {
log.info("========== 미필 비교 로직 종료 (명의이전 소유자 확인): {} ==========", vhclno);
return ownerTransferResult;
}
log.info("========== 미필 비교 로직 종료 (미적용): {} ==========", vhclno);
return null;
}

@ -27,6 +27,7 @@ public abstract class AbstractComparisonOmChecker implements ComparisonOmChecker
protected final VmisCarLedgerFrmbkLogService ledgerLogService;
protected static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
protected static final DateTimeFormatter DATE_FORMATTER_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* ()

@ -14,7 +14,8 @@ public interface ComparisonOmChecker {
*
*
* @param existingData (levyCrtrYmd )
* @param userOrgCd
* @return null ()
*/
String check(CarFfnlgTrgtIncmpVO existingData);
String check(CarFfnlgTrgtIncmpVO existingData, String userOrgCd);
}

@ -52,9 +52,9 @@ public class OwnerTransferOmChecker extends AbstractComparisonOmChecker {
}
@Override
public String check(CarFfnlgTrgtIncmpVO existingData) {
public String check(CarFfnlgTrgtIncmpVO existingData, String userOrgCd) {
String vhclno = existingData.getVhclno();
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 146일
String levyCrtrYmd = existingData.getLevyCrtrYmd().replace("-", ""); // 미필: 검사유효기간 종료일 + 146일
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
// 검사유효기간에서 시작일과 종료일 추출
@ -80,10 +80,24 @@ public class OwnerTransferOmChecker extends AbstractComparisonOmChecker {
}
try {
log.info("[명의이전-미필] Step 0: 자동차기본정보 조회 - 차량번호: {}, 현재일", vhclno);
NewBasicRequest step0Request = createBasicRequest(vhclno, null, LocalDate.now().format(DATE_FORMATTER));
NewBasicResponse step0Response = apiService.getBasicInfo(step0Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step0Response, existingData.getCarFfnlgTrgtIncmpId());
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[명의이전-미필] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+146일) ==========
log.info("[명의이전-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
log.info("[명의이전-미필] Step 1: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {}", step0vin, levyCrtrYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
NewBasicRequest step1Request = createBasicRequest(null, step0vin, levyCrtrYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
@ -211,29 +225,33 @@ public class OwnerTransferOmChecker extends AbstractComparisonOmChecker {
log.info("[명의이전-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
// ========== 시군구명 조회 ==========
String usgsrhldStdgCd = step1Record.getUsgsrhldStdgCd();
String sggCd = (usgsrhldStdgCd != null && usgsrhldStdgCd.length() >= 5) ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
String sggNm = carFfnlgTrgtIncmpMapper.selectSggNmBySggCd(sggCd);
String ownerNm = step1Record.getRprsOwnrNm();
// ========== 소유자명 상품용 포함 여부에 따라 처리 분기 ==========
String taskPrcsSttsCd;
String rmrk;
long daysBetween = 0; // 미필의 경우 일수차이는 0으로 설정
String rmrkDtl;
if (step4OwnerName != null && step4OwnerName.contains("상품용")) {
// 상품용 포함
log.info("[명의이전-미필] 명의이전 전 소유자가 상품용");
taskPrcsSttsCd = TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT; // 접수
rmrk = ComparisonOmRemarkBuilder.buildProductCloseLevyRemark(
step1Record, step4Record, targetRecord,
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
);
rmrk = ComparisonOmRemarkBuilder.buildProductCloseLevyRemark(targetRecord);
rmrkDtl = ComparisonOmRemarkBuilder.buildProductCloseLevyRemarkDetail(
vhclno, inspVldPrdEnd, levyCrtrYmd, step1OwnerName, targetChgYmd, step4OwnerName);
} else {
// 상품용 미포함
log.info("[명의이전-미필] 명의이전 전 소유자가 상품용 아님");
taskPrcsSttsCd = TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT; // 접수
rmrk = ComparisonOmRemarkBuilder.buildOwnerChangeRemark(
step1Record, step4Record, targetRecord,
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
);
rmrk = ComparisonOmRemarkBuilder.buildOwnerChangeRemark(targetRecord, sggNm, ownerNm, vhclno);
rmrkDtl = ComparisonOmRemarkBuilder.buildOwnerChangeRemarkDetail(
vhclno, inspVldPrdEnd, levyCrtrYmd, step1OwnerName, targetChgYmd, step4OwnerName);
}
log.info("[명의이전-미필] 모든 조건 충족! 차량번호: {}, 처리상태: {}", vhclno, taskPrcsSttsCd);
@ -242,13 +260,14 @@ public class OwnerTransferOmChecker extends AbstractComparisonOmChecker {
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(taskPrcsSttsCd);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
if (updateCount == 0) {

@ -53,9 +53,9 @@ public class ProductUseOmChecker extends AbstractComparisonOmChecker {
}
@Override
public String check(CarFfnlgTrgtIncmpVO existingData) {
public String check(CarFfnlgTrgtIncmpVO existingData, String userOrgCd) {
String vhclno = existingData.getVhclno();
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 146일
String levyCrtrYmd = existingData.getLevyCrtrYmd().replace("-", ""); // 미필: 검사유효기간 종료일 + 146일
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
// 검사유효기간에서 시작일과 종료일 추출
@ -76,10 +76,24 @@ public class ProductUseOmChecker extends AbstractComparisonOmChecker {
}
try {
log.info("[상품용-미필] Step 0: 자동차기본정보 조회 - 차량번호: {}, 현재일", vhclno);
NewBasicRequest step0Request = createBasicRequest(vhclno, null, LocalDate.now().format(DATE_FORMATTER));
NewBasicResponse step0Response = apiService.getBasicInfo(step0Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step0Response, existingData.getCarFfnlgTrgtIncmpId());
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[상품용-미필] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+146일) ==========
log.info("[상품용-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
log.info("[상품용-미필] Step 1: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {}", step0vin, levyCrtrYmd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
NewBasicRequest step1Request = createBasicRequest(null, step0vin, levyCrtrYmd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
@ -220,22 +234,22 @@ public class ProductUseOmChecker extends AbstractComparisonOmChecker {
log.info("[상품용-미필] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
// ========== 비고 생성 ==========
String rmrk = ComparisonOmRemarkBuilder.buildProductUseRemark(
step1Record, step4Record, targetRecord,
inspVldPrdEnd, levyCrtrYmd
);
String rmrk = ComparisonOmRemarkBuilder.buildProductUseRemark();
String rmrkDtl = ComparisonOmRemarkBuilder.buildProductUseRemarkDetail(
vhclno, inspVldPrdEnd, levyCrtrYmd, step1OwnerName, targetChgYmd, step4OwnerName);
// ========== DB 업데이트 ==========
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
if (updateCount == 0) {

@ -3,7 +3,6 @@ package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checke
import egovframework.constant.TaskPrcsSttsConstants;
import egovframework.exception.MessageException;
import egovframework.util.DateUtil;
import egovframework.util.SessionUtil;
import go.kr.project.api.model.request.NewBasicRequest;
import go.kr.project.api.model.response.NewBasicResponse;
import go.kr.project.api.service.ExternalVehicleApiService;
@ -12,7 +11,6 @@ import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
import go.kr.project.login.model.LoginUserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -46,7 +44,7 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
}
@Override
public String check(CarFfnlgTrgtIncmpVO existingData) {
public String check(CarFfnlgTrgtIncmpVO existingData, String userOrgCd) {
String vhclno = existingData.getVhclno();
try {
@ -60,16 +58,29 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
}
if (inspVldPrdEnd == null) {
log.debug("[이첩-미필] 검사유효기간 종료일 없음 - 차량번호: {}", vhclno);
log.info("[이첩-미필] 검사유효기간 종료일 없음 - 차량번호: {}", vhclno);
return null;
}
log.info("[상품용-미필] Step 0: 자동차기본정보 조회 - 차량번호: {}, 현재일", vhclno);
NewBasicRequest step0Request = createBasicRequest(vhclno, null, LocalDate.now().format(DATE_FORMATTER));
NewBasicResponse step0Response = apiService.getBasicInfo(step0Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step0Response, existingData.getCarFfnlgTrgtIncmpId());
if (step0Response == null || step0Response.getRecord() == null || step0Response.getRecord().isEmpty()) {
log.warn("[상품용-미필] Step 0 응답 없음 - 차량번호: {}", vhclno);
return null;
}
NewBasicResponse.Record step0Record = step0Response.getRecord().get(0);
String step0vin = step0Record.getVin(); // 차대번호
// 1단계: 자동차기본정보(차량번호, 부과일자=검사유효기간 + 146일)
// 부과일자는 이미 146일로 계산되어 있음
String step1Ymd = existingData.getLevyCrtrYmd();
log.info("[이첩-미필] 1단계 API 호출 - 차량번호: {}, 부과일자: {}", vhclno, step1Ymd);
NewBasicRequest step1Request = createBasicRequest(vhclno, null, step1Ymd);
log.info("[이첩-미필] 1단계 API 호출 - 차대번호: {}, 부과일자: {}", step0vin, step1Ymd);
NewBasicRequest step1Request = createBasicRequest(null, step0vin, step1Ymd);
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
@ -86,7 +97,7 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
// 소유자명에 "상품용" 포함 여부 체크
if (step1RprsOwnrNm != null && step1RprsOwnrNm.contains("상품용")) {
log.debug("[이첩-미필] 소유자명에 '상품용' 포함 - 차량번호: {}, 소유자명: {}", vhclno, step1RprsOwnrNm);
log.info("[이첩-미필] 소유자명에 '상품용' 포함 - 차량번호: {}, 소유자명: {}", vhclno, step1RprsOwnrNm);
return null;
}
@ -115,7 +126,7 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
// 2단계 비교: 1단계 소유자 = 2단계 소유자 동일 체크
if (step1RprsvOwnrIdecno != null && !step1RprsvOwnrIdecno.equals(step2RprsvOwnrIdecno)) {
log.debug("[이첩-미필] 1단계와 2단계 소유자 동일 - 차량번호: {}, 소유자: {}", vhclno, step1RprsOwnrNm);
log.info("[이첩-미필] 1단계와 2단계 소유자 동일 - 차량번호: {}, 소유자: {}", vhclno, step1RprsOwnrNm);
return null;
}
@ -140,30 +151,28 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
// 3단계 비교: 1단계 소유자 = 3단계 소유자 동일 체크
if (step1RprsvOwnrIdecno != null && !step1RprsvOwnrIdecno.equals(step3RprsvOwnrIdecno)) {
log.debug("[이첩-미필] 1단계와 3단계 소유자 동일 - 차량번호: {}, 소유자: {}", vhclno, step1RprsOwnrNm);
log.info("[이첩-미필] 1단계와 3단계 소유자 동일 - 차량번호: {}, 소유자: {}", vhclno, step1RprsOwnrNm);
return null;
}
// 법정동코드 유효성 검사
if (step1UsgsrhldStdgCd == null || step1UsgsrhldStdgCd.length() < 4) {
log.debug("[이첩-미필] 법정동코드 없음 - 차량번호: {}", vhclno);
log.info("[이첩-미필] 법정동코드 없음 - 차량번호: {}", vhclno);
return null;
}
// 세션에서 사용자 정보 조회
LoginUserVO userInfo = SessionUtil.getLoginUser();
if (userInfo == null || userInfo.getOrgCd() == null) {
log.debug("[이첩-미필] 사용자 정보 없음");
// 사용자 조직코드 유효성 검사
if (userOrgCd == null) {
log.info("[이첩-미필] 사용자 정보 없음");
return null;
}
// 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교
String legalDong4 = step1UsgsrhldStdgCd.substring(0, 4);
String userOrgCd = userInfo.getOrgCd();
String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd;
if (legalDong4.equals(userOrg4)) {
log.debug("[이첩-미필] 법정동코드 일치 - 차량번호: {}, 법정동: {}, 조직: {}",
log.info("[이첩-미필] 법정동코드 일치 - 차량번호: {}, 법정동: {}, 조직: {}",
vhclno, legalDong4, userOrg4);
return null;
}
@ -179,17 +188,23 @@ public class TransferOmChecker extends AbstractComparisonOmChecker {
sggNm = "";
}
// 비고 생성 (이첩-2 케이스)
String rmrk = ComparisonOmRemarkBuilder.buildTransferCase2Remark(sggNm, legalDong4);
// 소유자명
String ownerNm = step1RprsOwnrNm;
// 비고 생성 (이첩 케이스)
String rmrk = ComparisonOmRemarkBuilder.buildTransferRemark(sggNm, ownerNm);
String rmrkDtl = ComparisonOmRemarkBuilder.buildTransferRemarkDetail(
vhclno, inspVldPrdEnd, step1Ymd, ownerNm, step1UsgsrhldStdgCd, sggNm, userOrgCd);
// DB 업데이트
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER);
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER_DASH));
existingData.setCarBscMttrInqFlnm(step1RprsOwnrNm);
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
existingData.setRmrk(rmrk);
existingData.setRmrkDtl(rmrkDtl);
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
if (updateCount == 0) {

@ -163,7 +163,7 @@ vmis:
max-total: 100 # 최대 연결 수
max-per-route: 20 # 경로당 최대 연결 수
rate-limit:
permits-per-second: 5.0 # 초당 5건 제한
permits-per-second: 0 # 초당 5건 제한(5.0)
# External API 설정
external:

@ -108,40 +108,44 @@ car-ffnlg-txt-parse:
byte-size-2:
first-line: # 첫째줄 필드별 바이트 길이 (2바이트 기준)
inspstn-cd: 8 # 검사소 코드
insp-ymd: 12 # 검사일자
insp-ymd: 11 # 검사일자
vhclno-asterisk: 1 # 차량번호 앞 별표 공간
vhclno: 13 # 차량번호
ownr-nm: 31 # 소유자명
rrno: 15 # 주민등록번호
car-nm: 16 # 자동차명
car-knd: 14 # 자동차종류
car-usg: 11 # 자동차용도
insp-end-ymd: 12 # 검사종료일자
daycnt: 4 # 일수
insp-end-ymd: 11 # 검사종료일자
daycnt: 5 # 일수 (별표 포함)
ffnlg-amt: 10 # 과태료금액
second-line: # 둘째줄 필드별 바이트 길이 (2바이트 기준)
skip: 8 # 공백 (스킵)
second-line: # 둘째줄 필드별 바이트 길이 (2바이트 기준)
skip: 7 # 공백 (스킵)
last-reg-ymd-asterisk: 1 # 최종등록일 앞 별표 공간
last-reg-ymd: 12 # 최종등록일자
addr: 86 # 주소
vld-prd-expry-ymd: 12 # 유효기간만료일자
trd-gds: 11 # 매매상품
vld-prd-expry-ymd: 13 # 유효기간만료일자
trd-gds: 10 # 매매상품
# ===== 3바이트 환경 설정 (UTF-8) =====
byte-size-3:
first-line: # 첫째줄 필드별 바이트 길이 (2바이트 기준)
first-line: # 첫째줄 필드별 바이트 길이 (3바이트 기준)
inspstn-cd: 8 # 검사소 코드
insp-ymd: 12 # 검사일자
vhclno: 13 # 차량번호
vhclno-asterisk: 1 # 차량번호 앞 별표 공간
vhclno: 12 # 차량번호
ownr-nm: 31 # 소유자명
rrno: 15 # 주민등록번호
car-nm: 16 # 자동차명
car-knd: 14 # 자동차종류
car-usg: 11 # 자동차용도
insp-end-ymd: 12 # 검사종료일자
daycnt: 4 # 일수
daycnt: 4 # 일수 (별표 포함)
ffnlg-amt: 10 # 과태료금액
second-line: # 둘째줄 필드별 바이트 길이 (2바이트 기준)
second-line: # 둘째줄 필드별 바이트 길이 (3바이트 기준)
skip: 8 # 공백 (스킵)
last-reg-ymd: 12 # 최종등록일자
last-reg-ymd-asterisk: 1 # 최종등록일 앞 별표 공간
last-reg-ymd: 11 # 최종등록일자
addr: 86 # 주소
vld-prd-expry-ymd: 12 # 유효기간만료일자
trd-gds: 11 # 매매상품

@ -30,6 +30,12 @@
<if test='schInspYmdEnd != null and schInspYmdEnd != ""'>
AND t.INSP_YMD &lt;= #{schInspYmdEnd}
</if>
<if test='schReinspYn != null and schReinspYn.size() > 0'>
AND t.REINSP_YN IN
<foreach collection="schReinspYn" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
</sql>
<!-- 과태료 대상 목록 총 개수 조회 -->
@ -62,6 +68,7 @@
t.CAR_USG AS carUsg,
t.INSP_END_YMD AS inspEndYmd,
t.DAYCNT AS daycnt,
t.REINSP_YN AS reinspYn,
t.FFNLG_AMT AS ffnlgAmt,
t.LAST_REG_YMD AS lastRegYmd,
t.ADDR AS addr,
@ -70,6 +77,7 @@
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
t.TASK_PRCS_YMD AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
@ -117,6 +125,7 @@
t.CAR_USG AS carUsg,
t.INSP_END_YMD AS inspEndYmd,
t.DAYCNT AS daycnt,
t.REINSP_YN AS reinspYn,
t.FFNLG_AMT AS ffnlgAmt,
t.LAST_REG_YMD AS lastRegYmd,
t.ADDR AS addr,
@ -125,6 +134,7 @@
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
t.TASK_PRCS_YMD AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
@ -157,29 +167,31 @@
<!-- 과태료 대상 목록 엑셀 다운로드용 조회 -->
<select id="selectListForExcel" parameterType="CarFfnlgTrgtVO" resultType="CarFfnlgTrgtExcelVO">
SELECT
DATE_FORMAT(STR_TO_DATE(t.RCPT_YMD, '%Y%m%d'), '%Y-%m-%d') AS rcptYmd,
t.RCPT_YMD AS rcptYmd,
t.INSPSTN_CD AS inspstnCd,
DATE_FORMAT(STR_TO_DATE(t.INSP_YMD, '%Y%m%d'), '%Y-%m-%d') AS inspYmd,
t.INSP_YMD AS inspYmd,
t.VHCLNO AS vhclno,
t.OWNR_NM AS ownrNm,
ECL_DECRYPT(t.RRNO) AS rrno,
t.CAR_NM AS carNm,
t.CAR_KND AS carKnd,
t.CAR_USG AS carUsg,
DATE_FORMAT(STR_TO_DATE(t.INSP_END_YMD, '%Y%m%d'), '%Y-%m-%d') AS inspEndYmd,
t.INSP_END_YMD AS inspEndYmd,
t.DAYCNT AS daycnt,
t.REINSP_YN AS reinspYn,
t.FFNLG_AMT AS ffnlgAmt,
DATE_FORMAT(STR_TO_DATE(t.LAST_REG_YMD, '%Y%m%d'), '%Y-%m-%d') AS lastRegYmd,
t.LAST_REG_YMD AS lastRegYmd,
t.ADDR AS addr,
DATE_FORMAT(STR_TO_DATE(t.VLD_PRD_EXPRY_YMD, '%Y%m%d'), '%Y-%m-%d') AS vldPrdExpryYmd,
t.VLD_PRD_EXPRY_YMD AS vldPrdExpryYmd,
t.TRD_GDS AS trdGds,
cd.CD_NM AS taskPrcsSttsCdNm,
DATE_FORMAT(STR_TO_DATE(t.TASK_PRCS_YMD, '%Y%m%d'), '%Y-%m-%d') AS taskPrcsYmd,
t.TASK_PRCS_YMD AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
t.CAR_BSC_MTTR_INQ_SGG_NM AS carBscMttrInqSggNm,
t.CAR_REG_FRMBK_CHG_TASK_SE_NM AS carRegFrmbkChgTaskSeNm,
DATE_FORMAT(STR_TO_DATE(t.CAR_REG_FRMBK_CHG_YMD, '%Y%m%d'), '%Y-%m-%d') AS carRegFrmbkChgYmd,
t.CAR_REG_FRMBK_CHG_YMD AS carRegFrmbkChgYmd,
t.CAR_REG_FRMBK_DTL AS carRegFrmbkDtl,
DATE_FORMAT(t.REG_DT, '%Y-%m-%d %H:%i:%s') AS regDt,
u.USER_NM AS rgtrNm
@ -211,6 +223,7 @@
CAR_USG,
INSP_END_YMD,
DAYCNT,
REINSP_YN,
FFNLG_AMT,
LAST_REG_YMD,
ADDR,
@ -244,6 +257,7 @@
#{carUsg},
#{inspEndYmd},
#{daycnt},
#{reinspYn},
#{ffnlgAmt},
#{lastRegYmd},
#{addr},
@ -273,6 +287,7 @@
SET TASK_PRCS_STTS_CD = #{taskPrcsSttsCd},
TASK_PRCS_YMD = #{taskPrcsYmd},
RMRK = #{rmrk},
RMRK_DTL = #{rmrkDtl},
CAR_BASS_MATTER_INQIRE_ID = #{carBassMatterInqireId},
CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId},
CAR_BSC_MTTR_INQ_FLNM = #{carBscMttrInqFlnm},

@ -66,6 +66,7 @@
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
t.TASK_PRCS_YMD AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
@ -117,6 +118,7 @@
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
t.TASK_PRCS_YMD AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
@ -162,6 +164,7 @@
cd.CD_NM AS taskPrcsSttsCdNm,
DATE_FORMAT(STR_TO_DATE(t.TASK_PRCS_YMD, '%Y%m%d'), '%Y-%m-%d') AS taskPrcsYmd,
t.RMRK AS rmrk,
t.RMRK_DTL AS rmrkDtl,
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
t.CAR_BSC_MTTR_INQ_SGG_NM AS carBscMttrInqSggNm,
t.CAR_REG_FRMBK_CHG_TASK_SE_NM AS carRegFrmbkChgTaskSeNm,
@ -251,6 +254,7 @@
SET TASK_PRCS_STTS_CD = #{taskPrcsSttsCd},
TASK_PRCS_YMD = #{taskPrcsYmd},
RMRK = #{rmrk},
RMRK_DTL = #{rmrkDtl},
CAR_BASS_MATTER_INQIRE_ID = #{carBassMatterInqireId},
CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId},
CAR_BSC_MTTR_INQ_FLNM = #{carBscMttrInqFlnm},

@ -25,7 +25,7 @@
<ul class="lef">
<li class="th">접수일자</li>
<li>
<input type="text" id="schRcptYmdStart" name="schRcptYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}"/> ~
<input type="text" id="schRcptYmdStart" name="schRcptYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -10)}"/> ~
<input type="text" id="schRcptYmdEnd" name="schRcptYmdEnd" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateTime('yyyy-MM-dd')}"/>
</li>
<li class="th">검사일자</li>
@ -41,6 +41,12 @@
<li>
<input type="text" id="schOwnrNm" name="schOwnrNm" class="input" style="width: 150px;" maxlength="75" autocomplete="off"/>
</li>
</ul>
<ul class="rig2">
<li><button type="button" id="search_btn" class="newbtnss bg1">검색</button></li>
<li><button type="button" id="reset_btn" class="newbtnss bg5" style="margin-left: 5px;">초기화</button></li>
</ul>
<ul class="lef2">
<li class="th">처리상태</li>
<li>
<c:forEach var="code" items="${taskPrcsSttsCdList}">
@ -50,10 +56,15 @@
</label>
</c:forEach>
</li>
</ul>
<ul class="rig2">
<li><button type="button" id="search_btn" class="newbtnss bg1">검색</button></li>
<li><button type="button" id="reset_btn" class="newbtnss bg5" style="margin-left: 5px;">초기화</button></li>
<li class="th">재검여부</li>
<li>
<label style="margin-right: 10px; cursor: pointer;">
<input type="checkbox" name="schReinspYn" value="Y"/> 재검(Y)
</label>
<label style="margin-right: 10px; cursor: pointer;">
<input type="checkbox" name="schReinspYn" value="N"/> 미재검(N)
</label>
</li>
</ul>
</div>
<div class="gs_booking">
@ -70,12 +81,6 @@
<button type="button" id="btn_save" class="newbtn bg4">저장</button>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
<select id="perPageSelect" class="input" style="width: 112px; ">
<option value="15">페이지당 15</option>
<option value="50">페이지당 50</option>
<option value="100">페이지당 100</option>
</select>
<span class="page_number"><span id="currentPage"></span><span class="bar">/</span><span id="totalPages"></span> Pages</span>
</li>
</ul>
<div class="containers">
@ -113,13 +118,6 @@
// 그리드 조회 시 사용된 마지막 검색조건 저장
var LAST_GRID_SEARCH_COND = {};
// 페이징 정보를 저장할 전역 변수
var GRID_PAGINATION_INFO = {
totalCount: 0,
page: 0,
perPage: 0
};
// 검색정보 설정
var setSearchCond = function() {
var schRcptYmdStart = $.trim(nvl($("#schRcptYmdStart").val(), ""));
@ -135,13 +133,19 @@
schTaskPrcsSttsCd.push($(this).val());
});
SEARCH_COND.schRcptYmdStart = schRcptYmdStart.replace(/-/g, '');
SEARCH_COND.schRcptYmdEnd = schRcptYmdEnd.replace(/-/g, '');
SEARCH_COND.schInspYmdStart = schInspYmdStart.replace(/-/g, '');
SEARCH_COND.schInspYmdEnd = schInspYmdEnd.replace(/-/g, '');
var schReinspYn = [];
$("input[name='schReinspYn']:checked").each(function() {
schReinspYn.push($(this).val());
});
SEARCH_COND.schRcptYmdStart = schRcptYmdStart;
SEARCH_COND.schRcptYmdEnd = schRcptYmdEnd;
SEARCH_COND.schInspYmdStart = schInspYmdStart;
SEARCH_COND.schInspYmdEnd = schInspYmdEnd;
SEARCH_COND.schVhclno = schVhclno;
SEARCH_COND.schOwnrNm = schOwnrNm;
SEARCH_COND.schTaskPrcsSttsCd = schTaskPrcsSttsCd;
SEARCH_COND.schReinspYn = schReinspYn;
};
// 다운로드 URL 생성 (현재 검색조건을 쿼리스트링으로 부여)
@ -161,6 +165,11 @@
params.push('schTaskPrcsSttsCd=' + encodeURIComponent(val));
});
}
if (SEARCH_COND.schReinspYn && SEARCH_COND.schReinspYn.length > 0) {
SEARCH_COND.schReinspYn.forEach(function(val) {
params.push('schReinspYn=' + encodeURIComponent(val));
});
}
return baseUrl + (params.length ? ('?' + params.join('&')) : '');
};
@ -183,7 +192,8 @@
schInspYmdEnd: '검사일자 종료',
schVhclno: '차량번호',
schOwnrNm: '소유자명',
schTaskPrcsSttsCd: '처리상태'
schTaskPrcsSttsCd: '처리상태',
schReinspYn: '재검여부'
};
// 날짜 포맷 변환 함수 (YYYYMMDD -> YYYY-MM-DD)
@ -207,6 +217,22 @@
return textArray.join(', ');
};
// 재검여부 -> 텍스트 변환
var getReinspYnText = function(codeArray) {
if (!codeArray || codeArray.length === 0) return '전체';
var textArray = [];
codeArray.forEach(function(code) {
if (code === 'Y') {
textArray.push('재검(Y)');
} else if (code === 'N') {
textArray.push('미재검(N)');
} else {
textArray.push(code);
}
});
return textArray.join(', ');
};
// 값 포맷팅 함수
var formatValue = function(field, value) {
if (field.indexOf('Ymd') > -1) {
@ -215,6 +241,9 @@
if (field === 'schTaskPrcsSttsCd') {
return getTaskPrcsSttsCdText(value);
}
if (field === 'schReinspYn') {
return getReinspYnText(value);
}
if (!value || (Array.isArray(value) && value.length === 0)) {
return '(공백)';
}
@ -224,14 +253,14 @@
// 달라진 조건 찾기
var differences = [];
var searchFields = ['schRcptYmdStart', 'schRcptYmdEnd', 'schInspYmdStart', 'schInspYmdEnd',
'schVhclno', 'schOwnrNm', 'schTaskPrcsSttsCd'];
'schVhclno', 'schOwnrNm', 'schTaskPrcsSttsCd', 'schReinspYn'];
searchFields.forEach(function(field) {
var currentValue = SEARCH_COND[field];
var lastValue = LAST_GRID_SEARCH_COND[field];
// 배열 비교 (처리상태)
if (field === 'schTaskPrcsSttsCd') {
// 배열 비교 (처리상태, 재검여부)
if (field === 'schTaskPrcsSttsCd' || field === 'schReinspYn') {
currentValue = currentValue || [];
lastValue = lastValue || [];
@ -306,9 +335,6 @@
// 데이터 소스 설정
var dataSource = this.createDataSource();
// 현재 선택된 perPage 값 가져오기
var perPage = parseInt($('#perPageSelect').val() || 15, 10);
// 그리드 설정 객체 생성
var gridConfig = new XitTuiGridConfig();
@ -321,10 +347,9 @@
gridConfig.setOptUseClientSort(false);
// 페이징 옵션 설정
gridConfig.setOptPageOptions({
useClient: false,
perPage: perPage
});
//gridConfig.setOptPageOptions({
// useClient: true
//});
gridConfig.setOptColumnOptions({ //컬럼고정 옵션
frozenCount: 7 //고정컬럼 갯수
@ -350,22 +375,20 @@
width: 60,
sortable: false,
formatter: function(e) {
var totalCount = GRID_PAGINATION_INFO.totalCount;
var page = GRID_PAGINATION_INFO.page;
var perPage = GRID_PAGINATION_INFO.perPage;
var rowIndex = e.row.rowKey;
return totalCount - (page - 1) * perPage - rowIndex;
return e.row.rowKey+1;
}
},
{ header: '접수일자', name: 'rcptYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '검사소코드', name: 'inspstnCd', align: 'center', width: 100 },
{ header: '검사일자', name: 'inspYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '차량번호', name: 'vhclno', align: 'center', width: 100 },
@ -376,10 +399,12 @@
{ header: '자동차용도', name: 'carUsg', align: 'center', width: 100 },
{ header: '검사종료일자', name: 'inspEndYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '일수', name: 'daycnt', align: 'right', width: 60 },
{ header: '재검여부', name: 'reinspYn', align: 'center', width: 80 },
{ header: '과태료금액', name: 'ffnlgAmt', align: 'right', width: 100,
formatter: function(e) {
return e.value ? parseInt(e.value).toLocaleString() + '원' : '';
@ -387,19 +412,22 @@
},
{ header: '최종등록일자', name: 'lastRegYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '주소', name: 'addr', align: 'left', width: 250 },
{ header: '유효기간만료일자', name: 'vldPrdExpryYmd', align: 'center', width: 120,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '매매상품', name: 'trdGds', align: 'center', width: 100 },
{ header: '처리일자', name: 'taskPrcsYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{
@ -432,7 +460,7 @@
header: '비고',
name: 'rmrk',
align: 'left',
width: 200,
width: 300,
editor: 'text',
formatter: function(e) {
var rmrk = e.value || '';
@ -447,6 +475,23 @@
return displayText;
}
},
{
header: '비고 상세',
name: 'rmrkDtl',
align: 'left',
width: 200,
formatter: function(e) {
var rmrkDtl = e.value || '';
var displayText = rmrkDtl.length > 30 ? rmrkDtl.substring(0, 30) + '...' : rmrkDtl;
if (rmrkDtl && rmrkDtl.trim()) {
var escapedRmrkDtl = rmrkDtl.replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
return '<span class="mdi mdi-note-text" style="color: #4CAF50; cursor: pointer; margin-right: 5px;" onclick="showRmrkPopup(\'' + escapedRmrkDtl + '\')"></span>' +
'<span style="cursor: pointer;" onclick="showRmrkPopup(\'' + escapedRmrkDtl + '\')">' + displayText + '</span>';
}
return displayText;
}
},
{ header: '기본사항조회성명', name: 'carBscMttrInqFlnm', align: 'center', width: 100 },
{ header: '기본사항조회시군구코드', name: 'carBscMttrInqSggCd', align: 'center', width: 100, hidden: true },
{ header: '기본사항조회시군구명', name: 'carBscMttrInqSggNm', align: 'center', width: 120 },
@ -518,8 +563,6 @@
initialRequest: false,
serializer: function(params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
return $.param(SEARCH_COND);
}
};
@ -549,15 +592,10 @@
this.instance.on('successResponse', function(ev) {
var responseObj = JSON.parse(ev.xhr.response);
if(responseObj){
$("#currentPage").text(responseObj.data.pagination.page);
$("#totalPages").text(responseObj.data.pagination.totalPages);
var totalCount = responseObj.data.pagination.totalCount;
$("#totalCount").text('총 ' + totalCount.toLocaleString() + '건');
// 페이징 정보를 전역 변수에 저장
GRID_PAGINATION_INFO.totalCount = responseObj.data.pagination.totalCount;
GRID_PAGINATION_INFO.page = responseObj.data.pagination.page;
GRID_PAGINATION_INFO.perPage = responseObj.data.pagination.perPage;
if( responseObj.data && responseObj.data.contents ){
var totalCount = responseObj.data.contents.length;
$("#totalCount").text('총 ' + totalCount.toLocaleString() + '건');
}
}
// 선택된 행 초기화
@ -605,11 +643,8 @@
// 취소 확인
if (confirm('모든 변경사항을 취소하시겠습니까?')) {
// 현재 페이지 저장
var currentPage = this.instance.getPagination().getCurrentPage();
// 그리드 데이터를 다시 로드하여 모든 변경사항 취소
this.instance.readData(currentPage);
this.instance.readData(1);
}
},
@ -653,7 +688,6 @@
},
error: function(xhr, status, error) {
console.error("저장 실패:", error);
alert("저장 중 오류가 발생했습니다.");
}
});
}
@ -684,7 +718,7 @@
// 초기화 버튼 클릭
$("#reset_btn").on('click', function() {
$("#schRcptYmdStart").val("${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}");
$("#schRcptYmdStart").val("${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -10)}");
$("#schRcptYmdEnd").val("${dateUtil:getCurrentDateTime('yyyy-MM-dd')}");
$("#schInspYmdStart").val("");
$("#schInspYmdEnd").val("");
@ -692,6 +726,7 @@
$("#schOwnrNm").val("");
// 체크박스 모두 해제
$("input[name='schTaskPrcsSttsCd']").prop('checked', false);
$("input[name='schReinspYn']").prop('checked', false);
self.grid.reload();
});
@ -743,7 +778,7 @@
// 검색 조건 파라미터 추가
setSearchCond();
$.each(SEARCH_COND, function(key, value) {
if (key === 'schTaskPrcsSttsCd') {
if (key === 'schTaskPrcsSttsCd' || key === 'schReinspYn') {
// 배열인 경우 각각 추가
if (value && value.length > 0) {
value.forEach(function(val) {

@ -25,7 +25,7 @@
<ul class="lef">
<li class="th">접수일자</li>
<li>
<input type="text" id="schRcptYmdStart" name="schRcptYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}"/> ~
<input type="text" id="schRcptYmdStart" name="schRcptYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -10)}"/> ~
<input type="text" id="schRcptYmdEnd" name="schRcptYmdEnd" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateTime('yyyy-MM-dd')}"/>
</li>
<li class="th">차량번호</li>
@ -65,12 +65,6 @@
<button type="button" id="btn_save" class="newbtn bg4">저장</button>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
<select id="perPageSelect" class="input" style="width: 112px; ">
<option value="15">페이지당 15</option>
<option value="50">페이지당 50</option>
<option value="100">페이지당 100</option>
</select>
<span class="page_number"><span id="currentPage"></span><span class="bar">/</span><span id="totalPages"></span> Pages</span>
</li>
</ul>
<div class="containers">
@ -108,13 +102,6 @@
// 그리드 조회 시 사용된 마지막 검색조건 저장
var LAST_GRID_SEARCH_COND = {};
// 페이징 정보를 저장할 전역 변수
var GRID_PAGINATION_INFO = {
totalCount: 0,
page: 0,
perPage: 0
};
// 검색정보 설정
var setSearchCond = function() {
var schRcptYmdStart = $.trim(nvl($("#schRcptYmdStart").val(), ""));
@ -128,8 +115,8 @@
schTaskPrcsSttsCd.push($(this).val());
});
SEARCH_COND.schRcptYmdStart = schRcptYmdStart.replace(/-/g, '');
SEARCH_COND.schRcptYmdEnd = schRcptYmdEnd.replace(/-/g, '');
SEARCH_COND.schRcptYmdStart = schRcptYmdStart;
SEARCH_COND.schRcptYmdEnd = schRcptYmdEnd;
SEARCH_COND.schVhclno = schVhclno;
SEARCH_COND.schOwnrNm = schOwnrNm;
SEARCH_COND.schTaskPrcsSttsCd = schTaskPrcsSttsCd;
@ -308,10 +295,10 @@
gridConfig.setOptUseClientSort(false);
// 페이징 옵션 설정
gridConfig.setOptPageOptions({
useClient: false,
perPage: perPage
});
//setOptPageOptionsgridConfig.setOptPageOptions({
// useClient: false,
// perPage: perPage
//});
gridConfig.setOptColumnOptions({ //컬럼고정 옵션
frozenCount: 5 //고정컬럼 갯수
@ -337,16 +324,13 @@
width: 60,
sortable: false,
formatter: function(e) {
var totalCount = GRID_PAGINATION_INFO.totalCount;
var page = GRID_PAGINATION_INFO.page;
var perPage = GRID_PAGINATION_INFO.perPage;
var rowIndex = e.row.rowKey;
return totalCount - (page - 1) * perPage - rowIndex;
return e.row.rowKey + 1;
}
},
{ header: '접수일자', name: 'rcptYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{ header: '프로그램ID', name: 'prgrmId', align: 'center', width: 100 },
@ -359,12 +343,15 @@
{ header: '검사유효기간', name: 'inspVldPrd', align: 'center', width: 200 },
{ header: 'API 호출 부과일자', name: 'levyCrtrYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
, hidden : true
},
{ header: '업무처리일자', name: 'taskPrcsYmd', align: 'center', width: 100,
formatter: function(e) {
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
//return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
return e.value;
}
},
{
@ -397,7 +384,7 @@
header: '비고',
name: 'rmrk',
align: 'left',
width: 200,
width: 300,
editor: 'text',
formatter: function(e) {
var rmrk = e.value || '';
@ -412,6 +399,23 @@
return displayText;
}
},
{
header: '비고 상세',
name: 'rmrkDtl',
align: 'left',
width: 200,
formatter: function(e) {
var rmrkDtl = e.value || '';
var displayText = rmrkDtl.length > 30 ? rmrkDtl.substring(0, 30) + '...' : rmrkDtl;
if (rmrkDtl && rmrkDtl.trim()) {
var escapedRmrkDtl = rmrkDtl.replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
return '<span class="mdi mdi-note-text" style="color: #4CAF50; cursor: pointer; margin-right: 5px;" onclick="showRmrkPopup(\'' + escapedRmrkDtl + '\')"></span>' +
'<span style="cursor: pointer;" onclick="showRmrkPopup(\'' + escapedRmrkDtl + '\')">' + displayText + '</span>';
}
return displayText;
}
},
{ header: '기본사항조회성명', name: 'carBscMttrInqFlnm', align: 'center', width: 100 },
{ header: '기본사항조회시군구코드', name: 'carBscMttrInqSggCd', align: 'center', width: 100, hidden: true },
{ header: '기본사항조회시군구명', name: 'carBscMttrInqSggNm', align: 'center', width: 120 },
@ -514,15 +518,10 @@
this.instance.on('successResponse', function(ev) {
var responseObj = JSON.parse(ev.xhr.response);
if(responseObj){
$("#currentPage").text(responseObj.data.pagination.page);
$("#totalPages").text(responseObj.data.pagination.totalPages);
var totalCount = responseObj.data.pagination.totalCount;
$("#totalCount").text('총 ' + totalCount.toLocaleString() + '건');
// 페이징 정보를 전역 변수에 저장
GRID_PAGINATION_INFO.totalCount = responseObj.data.pagination.totalCount;
GRID_PAGINATION_INFO.page = responseObj.data.pagination.page;
GRID_PAGINATION_INFO.perPage = responseObj.data.pagination.perPage;
if( responseObj.data && responseObj.data.contents ){
var totalCount = responseObj.data.contents.length;
$("#totalCount").text('총 ' + totalCount.toLocaleString() + '건');
}
}
// 선택된 행 초기화
@ -570,11 +569,8 @@
// 취소 확인
if (confirm('모든 변경사항을 취소하시겠습니까?')) {
// 현재 페이지 저장
var currentPage = this.instance.getPagination().getCurrentPage();
// 그리드 데이터를 다시 로드하여 모든 변경사항 취소
this.instance.readData(currentPage);
this.instance.readData(1);
}
},
@ -649,7 +645,7 @@
// 초기화 버튼 클릭
$("#reset_btn").on('click', function() {
$("#schRcptYmdStart").val("${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}");
$("#schRcptYmdStart").val("${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -10)}");
$("#schRcptYmdEnd").val("${dateUtil:getCurrentDateTime('yyyy-MM-dd')}");
$("#schVhclno").val("");
$("#schOwnrNm").val("");

Loading…
Cancel
Save