From e20aeba146427f0c76e8779c1e71e77ea4d20bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EC=98=81?= Date: Tue, 2 Dec 2025 17:14:03 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=91=EC=85=80=EB=8B=A4=EC=9A=B4=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80,=20=EB=A1=9C=EC=A7=81=20md=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9E=AC=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/자동차과태료_비교로직_정리.md | 485 +++++++++++------- .../controller/CarFfnlgTrgtController.java | 36 ++ .../mapper/CarFfnlgTrgtMapper.java | 8 + .../model/CarFfnlgTrgtExcelVO.java | 124 +++++ .../service/CarFfnlgTrgtService.java | 8 + .../service/impl/CarFfnlgTrgtServiceImpl.java | 11 + .../history/VehicleApiHistoryMapper_maria.xml | 4 +- .../registration/CarFfnlgTrgtMapper_maria.xml | 42 ++ .../registration/list.jsp | 36 +- 9 files changed, 554 insertions(+), 200 deletions(-) create mode 100644 src/main/java/go/kr/project/carInspectionPenalty/registration/model/CarFfnlgTrgtExcelVO.java diff --git a/docs/자동차과태료_비교로직_정리.md b/docs/자동차과태료_비교로직_정리.md index f5d9fd0..d93a37b 100644 --- a/docs/자동차과태료_비교로직_정리.md +++ b/docs/자동차과태료_비교로직_정리.md @@ -16,13 +16,14 @@ | 일자 | 변경 내용 | 비고 | |------|----------|------| +| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 30일 이내 조건 추가 | | 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 | -| 2025-01-XX | 문서 업데이트 완료 | 신규 로직 반영 및 코드 예시 추가 | ### 처리 규칙 > **중요**: 순서가 중요함! +> - 1. 상품용 검증 (Case1) → 2. 내사종결 상품용 검증 (Case2) → 3. 이첩 검증 > - 조건에 걸리는 순간 다음 차량번호 비교로 진행 > - 각 비교 로직별로 개별 API 호출 수행 @@ -30,9 +31,10 @@ ## 비교 로직 상세 -### 1. 상품용 검증 (2025년 신규 로직) +### 1. 상품용 검증 Case1 - 검사일 소유자가 상품용 -**필요 API**: 자동차기본정보 (3회), 자동차등록원부(갑) (1회) +**처리상태코드**: `02` (상품용) +**메서드**: `checkProductUseCase1()` (ComparisonServiceImpl.java:113~306) #### API 호출 순서 @@ -46,34 +48,110 @@ #### 비교 조건 (순차 실행) ```java -// 조건 1: 소유자명에 '상품용' 포함 여부 -if (!api1Response.소유자명.contains("상품용")) { - return false; // 미충족 +// 조건 1: 소유자명에 '상품용' 포함 여부 (Line 138-142) +if (!step1Record.소유자명.contains("상품용")) { + return null; // 미충족 } -// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 +// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 (Line 187-234) LocalDate latestChgDate = null; -LedgerRecord targetRecord = null; +NewLedgerResponse.Record targetRecord = null; -for (LedgerRecord record : 갑부상세List) { - // 2-1. 명의이전 코드 확인 - if (!record.CHG_TASK_SE_CD.equals("11")) { +for (NewLedgerResponse.Record record : ledgerRecords) { + // 2-1. 명의이전 코드 확인 (Line 199) + if (!"11".equals(record.CHG_TASK_SE_CD)) { continue; // 명의이전이 아니면 스킵 } - // 2-2. CHG_YMD <= 검사종료일자 + // 2-2. CHG_YMD <= 검사종료일자 (Line 208-213) if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사종료일자) { continue; // 검사종료일자 이후면 스킵 } - // 일단 주석처리 2-3. CHG_YMD <= 검사일 (검사일 이전일자만) - /* - if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사일) { - continue; // 검사일 이후면 스킵 + // 주석처리됨: CHG_YMD <= 검사일 조건 (Line 215-221) + // → 검사일 이후 데이터도 포함 (검사종료일자까지만 제한) + + // 2-3. 가장 마지막 일자 찾기 (Line 224-228) + if (latestChgDate == null || record.CHG_YMD > latestChgDate) { + latestChgDate = record.CHG_YMD; + targetRecord = record; } - */ +} + +if (targetRecord == null) { + return null; // 조건에 맞는 레코드 없음 +} + +// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명 (Line 257-260) +if (!step4Record.소유자명.equals(step1Record.소유자명)) { + return null; // 소유자명 불일치 +} + +// 주석처리됨: CHG_YMD 기간 범위 확인 (Line 264-273) +// → 기간 범위 체크하지 않음 + +return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE; // 상품용 조건 충족 +``` + +#### 주요 변경사항 + +1. **주석처리된 조건**: + - ~~`CHG_YMD <= 검사일` (검사일 이전일자 중)~~ → 제거됨 + - ~~`CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내`~~ → 제거됨 + +2. **적용되는 조건**: + - `CHG_YMD <= 검사종료일자` (검사종료일자 이전) + - 명의이전(코드 11) 레코드 중 가장 마지막 일자 + - 소유자명 일치 확인 + +#### 결과 처리 + +- **업무 처리 상태 코드**: `02` (상품용) +- **비고 컬럼 형식**: (Line 732-761, 대부분 주석처리됨) +``` +상품용 - 상품용검사 +``` + +--- - // 2-4. 가장 마지막 일자 찾기 +### 2. 내사종결 상품용 검증 Case2 - 명의이전 후 상품용 + +**처리상태코드**: `04` (내사종결) +**메서드**: `checkCloseProductUse()` (ComparisonServiceImpl.java:329~515) + +#### API 호출 순서 + +| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 | +|------|-----|--------------|-------------|------| +| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 | +| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘일자` | `차량번호`, `성명`, `민원인주민번호`, `민원인법정동코드` | 현재 소유자 정보 | +| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.민원인주민번호`, `2.민원인법정동코드` | 갑부 상세 List | 명의이전 이력 조회 | +| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 | + +#### 비교 조건 (순차 실행) + +```java +// 조건 1: 소유자명에 '상품용' 미포함 (Line 354-358) +if (step1Record.소유자명 == null || step1Record.소유자명.contains("상품용")) { + return null; // 미충족 (검사일 소유자가 상품용이면 Case1에서 처리됨) +} + +// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 (Line 403-437) +LocalDate latestChgDate = null; +NewLedgerResponse.Record targetRecord = null; + +for (NewLedgerResponse.Record record : ledgerRecords) { + // 2-1. 명의이전 코드 확인 (Line 415) + if (!"11".equals(record.CHG_TASK_SE_CD)) { + continue; // 명의이전이 아니면 스킵 + } + + // 2-2. CHG_YMD <= 검사일자 (Line 424-429) + if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사일자) { + continue; // 검사일자 이후면 스킵 + } + + // 2-3. 가장 마지막 일자 찾기 (Line 432-436) if (latestChgDate == null || record.CHG_YMD > latestChgDate) { latestChgDate = record.CHG_YMD; targetRecord = record; @@ -81,55 +159,50 @@ for (LedgerRecord record : 갑부상세List) { } if (targetRecord == null) { - return false; // 조건에 맞는 레코드 없음 + return null; // 조건에 맞는 레코드 없음 } -// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명 -if (!api4Response.소유자명.equals(api1Response.소유자명)) { - return false; // 소유자명 불일치 +// 조건 3: 명의이전일자가 검사일의 30일 이내인지 확인 (Line 444-450) +long daysBetween = ChronoUnit.DAYS.between(latestChgDate, 검사일); +if (daysBetween < 0 || daysBetween > 30) { + return null; // 30일 초과 } -// 일단 주석처리 조건 4: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내 -/* -if (targetRecord.CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 - && targetRecord.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) { - return true; // 상품용 조건 충족 +// 조건 4: 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 '상품용' 포함 (Line 476-482) +// Step 4 API: 부과일자 = CHG_YMD - 1일 (Line 457-464) +if (step4Record.소유자명 == null || !step4Record.소유자명.contains("상품용")) { + return null; // 명의이전 직전 소유자가 상품용이 아님 } -*/ - -return false; // 미충족 + +return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; // 내사종결 조건 충족 ``` +#### 로직 설명 + +이 로직은 **검사일 당시에는 상품용이 아니었지만, 명의이전 이전(30일 이내)에는 상품용이었던 경우**를 처리합니다: +1. 검사일 소유자가 상품용이 아님 +2. 검사일 이전 30일 이내에 명의이전(코드 11) 발생 +3. 명의이전 직전 소유자가 상품용 + #### 결과 처리 -- **업무 처리 상태 코드**: `02` (상품용) -- **비고 컬럼 형식**: +- **업무 처리 상태 코드**: `04` (내사종결) +- **비고 컬럼 형식**: (Line 784-818) ``` -상품용 - -■ 검사일 기준 소유자정보 - - 소유자명: {소유자명} - - 차대번호: {차대번호} - -■ 명의이전 시점 소유자정보 - - 소유자명: {소유자명} - - 조회일자: {CHG_YMD} - -■ 갑부 상세 (명의이전 이력) - - 변경일자: {CHG_YMD} - - 변경업무코드: 11 - - 변경업무명: 명의이전 - - 접수번호: {접수번호} - -■ 비교 기간 - - 유효기간만료일: {유효기간만료일} - - 검사종료일자: {검사종료일자} - - 판정: 명의이전일자가 기간 내 존재하고 검사일 소유자명과 일치 +명의이전(25.9.3.) 이전소유자 상품용 +22루2283 + - 검사기간: 2024-08-01 - 2024-08-31 + - 검사일: 2024-08-15 + - 명의이전: 2024-09-03 + - 상품용: 2024-09-03 ``` --- -### 2. 이첩 검증 (이첩-1, 이첩-2 병합 로직) +### 3. 이첩 검증 (이첩-1, 이첩-2 병합 로직) + +**처리상태코드**: `03` (이첩) +**메서드**: `checkTransferCase115Day()` (ComparisonServiceImpl.java:531~648) **필요 API**: 자동차기본정보 @@ -239,67 +312,111 @@ private boolean checkTransferCondition_LegalDongMismatch( [차량번호 조회] │ ▼ -┌─────────────────────────────────────────────────────┐ -│ 1. 상품용 검증 (4단계 API 호출) │ -├─────────────────────────────────────────────────────┤ -│ Step 1: 자동차기본정보(차량번호, 검사일) │ -│ → 소유자명에 "상품용" 포함? │ -│ │ │ -│ ├─ (No) ──────────────────────────┐ │ -│ │ │ │ -│ └─ (Yes) │ │ -│ ▼ │ │ -│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │ -│ → 현재 소유자 정보 │ │ -│ │ │ │ -│ ▼ │ │ -│ Step 3: 자동차등록원부(갑) 조회 │ │ -│ → 갑부 상세 List │ │ -│ │ │ │ -│ ▼ │ │ -│ 검사일 이전 가장 마지막 명의이전(11) │ │ -│ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │ -│ │ │ │ -│ ├─ (없음) ─────────────────────┐ │ │ -│ │ │ │ │ -│ └─ (발견) │ │ │ -│ ▼ │ │ │ -│ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │ -│ → 명의이전 시점 소유자명 │ │ │ -│ │ │ │ │ -│ ▼ │ │ │ -│ 소유자명 일치? && 기간 내 존재? │ │ │ -│ │ │ │ │ -│ ├─ (No) ───────────────────┐ │ │ │ -│ │ │ │ │ │ -│ └─ (Yes) │ │ │ │ -│ ▼ │ │ │ │ -│ [상품용 처리 완료] │ │ │ │ -│ │ │ │ │ │ -│ └──> [다음 차량] <──────────┴───┴───┘ │ -└─────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────┐ +│ 1. 상품용 Case1 - 검사일 소유자가 상품용 │ +├────────────────────────────────────────────────────────────┤ +│ Step 1: 자동차기본정보(차량번호, 검사일) │ +│ → 소유자명에 "상품용" 포함? │ +│ │ │ +│ ├─ (No) ──────────────────────────────────┐ │ +│ │ │ │ +│ └─ (Yes) │ │ +│ ▼ │ │ +│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │ +│ → 현재 소유자 정보 │ │ +│ ▼ │ │ +│ Step 3: 자동차등록원부(갑) 조회 │ │ +│ → 갑부 상세 List │ │ +│ ▼ │ │ +│ 검사종료일자 이전 가장 마지막 명의이전(11) │ │ +│ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │ +│ │ │ │ +│ ├─ (없음) ─────────────────────────────┐ │ │ +│ │ │ │ │ +│ └─ (발견) │ │ │ +│ ▼ │ │ │ +│ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │ +│ → 명의이전 시점 소유자명 │ │ │ +│ │ │ │ │ +│ ▼ │ │ │ +│ 소유자명 일치? │ │ │ +│ │ │ │ │ +│ ├─ (No) ─────────────────────────┐ │ │ │ +│ │ │ │ │ │ +│ └─ (Yes) │ │ │ │ +│ ▼ │ │ │ │ +│ [상품용(02) 처리 완료] │ │ │ │ +│ │ │ │ │ │ +│ └──> [다음 차량] <────────────────┴─────┴───┘ │ +└────────────────────────────────────────────────────────────┘ │ │ (조건 미충족) ▼ -┌─────────────────────────────────────────────────────┐ -│ 2. 이첩 검증 (이첩-1, 이첩-2) │ -├─────────────────────────────────────────────────────┤ -│ DAYCNT 확인 │ -│ │ │ -│ ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일+115일] │ -│ │ │ -│ └─ (<= 115) ─> [이첩-1: 부과기준일 = 검사일] │ -│ │ │ -│ ▼ │ -│ [자동차기본정보 API 호출] │ -│ │ │ -│ ▼ │ -│ [법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리] │ -│ │ │ -│ ├─ (불일치) ──> [이첩 처리 완료] ──> [다음 차량] │ -│ │ │ -│ └─ (일치) ────> [조건 미충족] ──> [다음 차량] │ -└─────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────┐ +│ 2. 내사종결 Case2 - 명의이전 후 상품용 │ +├────────────────────────────────────────────────────────────┤ +│ Step 1: 자동차기본정보(차량번호, 검사일) │ +│ → 소유자명에 "상품용" 미포함? │ +│ │ │ +│ ├─ (No) ──────────────────────────────────┐ │ +│ │ │ │ +│ └─ (Yes) │ │ +│ ▼ │ │ +│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │ +│ → 현재 소유자 정보 │ │ +│ ▼ │ │ +│ Step 3: 자동차등록원부(갑) 조회 │ │ +│ → 갑부 상세 List │ │ +│ ▼ │ │ +│ 검사일 이전 가장 마지막 명의이전(11) │ │ +│ 레코드 찾기 (CHG_YMD <= 검사일) │ │ +│ │ │ │ +│ ├─ (없음) ─────────────────────────────┐ │ │ +│ │ │ │ │ +│ └─ (발견) │ │ │ +│ ▼ │ │ │ +│ 명의이전일자가 검사일의 30일 이내? │ │ │ +│ │ │ │ │ +│ ├─ (No) ─────────────────────────┐ │ │ │ +│ │ │ │ │ │ +│ └─ (Yes) │ │ │ │ +│ ▼ │ │ │ │ +│ Step 4: 자동차기본정보(차대번호, CHG_YMD-1일) │ │ │ │ +│ → 명의이전 직전 소유자명 │ │ │ │ +│ │ │ │ │ │ +│ ▼ │ │ │ │ +│ 소유자명에 "상품용" 포함? │ │ │ │ +│ │ │ │ │ │ +│ ├─ (No) ───────────────────┐ │ │ │ │ +│ │ │ │ │ │ │ +│ └─ (Yes) │ │ │ │ │ +│ ▼ │ │ │ │ │ +│ [내사종결(04) 처리 완료] │ │ │ │ │ +│ │ │ │ │ │ │ +│ └──> [다음 차량] <──────────┴─────┴─────┴───┘ │ +└────────────────────────────────────────────────────────────┘ + │ + │ (조건 미충족) + ▼ +┌────────────────────────────────────────────────────────────┐ +│ 3. 이첩 검증 (이첩-1, 이첩-2) │ +├────────────────────────────────────────────────────────────┤ +│ DAYCNT 확인 │ +│ │ │ +│ ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일+115일] │ +│ │ │ +│ └─ (<= 115) ─> [이첩-1: 부과기준일 = 검사일] │ +│ │ │ +│ ▼ │ +│ [자동차기본정보 API 호출] │ +│ │ │ +│ ▼ │ +│ [법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리] │ +│ │ │ +│ ├─ (불일치) ──> [이첩(03) 처리 완료] ──> [다음 차량] │ +│ │ │ +│ └─ (일치) ────> [조건 미충족] ──> [다음 차량] │ +└────────────────────────────────────────────────────────────┘ ``` --- @@ -308,18 +425,27 @@ private boolean checkTransferCondition_LegalDongMismatch( ### 공통 주의사항 1. **API 호출 순서 준수**: 각 검증 단계별로 필요한 API만 호출 -2. **조건 우선순위**: 상품용 > 이첩 순서로 검증 +2. **조건 우선순위**: 상품용 Case1 > 내사종결 Case2 > 이첩 순서로 검증 3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동 4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록 5. **날짜 형식 지원**: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 (`DateUtil` 사용) -### 상품용 검증 주의사항 +### 상품용 Case1 주의사항 1. **4단계 API 호출 필수**: Step 1~4 모두 순차적으로 실행 -2. **가장 마지막 명의이전 레코드**: 검사일 이전일자 중 CHG_YMD가 가장 큰 값 선택 +2. **가장 마지막 명의이전 레코드**: 검사종료일자 이전일자 중 CHG_YMD가 가장 큰 값 선택 3. **소유자명 정확한 일치**: Step1 소유자명 == Step4 소유자명 (완전 일치) -4. **기간 범위 확인**: CHG_YMD가 유효기간만료일 ~ 검사종료일자 사이에 존재해야 함 +4. **주석처리된 조건**: + - ~~검사일 이전일자 제한~~ → 검사종료일자 이전일자만 제한 + - ~~기간 범위 확인~~ → 제거됨 5. **로그 상세 기록**: 각 단계별 결과를 상세히 로깅하여 디버깅 지원 +### 내사종결 Case2 주의사항 +1. **검사일 소유자 확인**: 검사일 소유자가 상품용이 아니어야 함 (Case1과 구분) +2. **30일 이내 조건**: 명의이전일자가 검사일의 30일 이내여야 함 +3. **CHG_YMD-1일 조회**: Step 4에서 명의이전 직전 시점(CHG_YMD - 1일) 조회 +4. **검사일 기준**: 갑부 레코드는 검사일 이전일자 중 가장 마지막 일자 선택 +5. **비고 형식**: 명의이전 날짜, 차량번호, 검사기간 등 상세 정보 포함 + ### 이첩 검증 주의사항 1. **법정동코드 길이 검증**: 최소 4자리 이상 필요 2. **DAYCNT 기준 명확**: > 115 (초과), <= 115 (이하) 구분 @@ -328,104 +454,69 @@ private boolean checkTransferCondition_LegalDongMismatch( -## 내가 text 로 정리한 내용 - -### 1. 상품용 (2025년 신규 로직) - -#### 필요한 API 정보 -1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일] - → response.차대번호, response.소유자명 +## 요약 정리 -2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] - → response.차량번호, response.성명, response.민원인주민번호, response.민원인법정동코드 - -3. 자동차등록원본(갑) api 호출 [2.response.차량번호, 2.response.성명, 2.response.민원인주민번호, 2.response.민원인법정동코드] - → 갑부 상세 List - -4. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:조건에맞는CHG_YMD] - → response.소유자명 - -#### 비교 로직 순서 -1. api-1번호출.소유자명.contains("상품용") - 1차 필터링 -2. 갑부 상세 List에서 조건에 맞는 명의이전 레코드 찾기: - - record.CHG_TASK_SE_CD == "11" (명의이전 코드) - - record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자 - - 일단 주석처리 : record.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사일 (검사일 이전일자 중) - - 위 조건 중 가장 마지막 일자 선택 -3. 4번 API로 명의이전 시점 소유자명 조회 -4. if (api-4번호출.소유자명 == api-1번호출.소유자명) { - return true; // 상품용 조건 충족 - /*일단 주석처리 - if (조건에맞는CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 - && 조건에맞는CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) { - return true; // 상품용 조건 충족 - } - */ - } -5. TB_CAR_FFNLG_TRGT 업무처리상태코드: 02 (상품용) -6. TB_CAR_FFNLG_TRGT 비고: 상품용 상세 정보 (위 참조) - - -### 2. 이첩1, 2 병합로직 - -#### 부과기준일 결정 -부과기준일 구하는 부분만 다르고 나머지 법정동코드 비교로직은 동일 +### 비교 로직 실행 순서 (executeComparison) ```java -if (TB_CAR_FFNLG_TRGT.DAYCNT > 115) { - 부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자 + 115일 - // 이첩-2 - 비고 = "전라남도 순천시 / 김정대, 115일 도래지, [법정동코드 및 법정동명]" -} else { - 부과기준일 = TB_CAR_FFNLG_TRGT.검사일자 - // 이첩-1 - 비고 = "서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" +// ComparisonServiceImpl.java Line 50-85 +public String executeComparison(CarFfnlgTrgtVO existingData) { + // 1. 상품용 Case1 - 검사일 소유자가 상품용 + String result1 = checkProductUseCase1(existingData); + if (result1 != null) return result1; // "02" + + // 2. 내사종결 Case2 - 명의이전 후 상품용 + String result2 = checkCloseProductUse(existingData); + if (result2 != null) return result2; // "04" + + // 3. 이첩 검증 + String result3 = checkTransferCase115Day(existingData); + if (result3 != null) return result3; // "03" + + // 모든 조건 미충족 + return null; } ``` -#### 법정동코드 비교 (이첩1, 2 공용) +### 처리상태코드 매핑 -```java -// 자동차기본정보 API 호출 -자동차기본정보API.call(부과기준일, 차량번호); +| 코드 | 상태명 | 처리 로직 | 메서드 | +|------|--------|----------|--------| +| 02 | 상품용 | 검사일 소유자가 상품용 | `checkProductUseCase1()` | +| 04 | 내사종결 | 명의이전(30일 이내) 후 상품용 | `checkCloseProductUse()` | +| 03 | 이첩 | 법정동코드 불일치 | `checkTransferCase115Day()` | -// 법정동코드 유효성 검사 -if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) { - return false; -} - -// 사용자 정보 조회 -SystemUserVO userInfo = userMapper.selectUser(userId); -if (userInfo == null || userInfo.getOrgCd() == null) { - return false; -} +### 주요 차이점 정리 -// 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교 -String legalDong4 = useStrnghldLegaldongCode.substring(0, 4); -String userOrgCd = userInfo.getOrgCd(); -String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd; - -if (legalDong4.equals(userOrg4)) { - // 법정동코드 일치 - 이첩 대상 아님 - return false; -} - -// 법정동코드 불일치 - 이첩 대상 -return true; -``` +| 구분 | Case1 (상품용) | Case2 (내사종결) | +|------|---------------|-----------------| +| 검사일 소유자 | 상품용 포함 | 상품용 미포함 | +| 갑부 레코드 기준 | CHG_YMD <= 검사종료일자 | CHG_YMD <= 검사일 | +| 추가 조건 | - | 명의이전일자가 검사일의 30일 이내 | +| Step 4 조회일 | CHG_YMD | CHG_YMD - 1일 | +| Step 4 확인사항 | 소유자명 일치 | 소유자명에 "상품용" 포함 | +| 처리상태코드 | 02 | 04 | --- ## 구현 완료 상태 -### ✅ 상품용 로직 +### ✅ 상품용 Case1 로직 - **구현 완료**: 신규 로직으로 구현 완료 -- **구현 위치**: `ComparisonServiceImpl.checkProductUse()` +- **구현 위치**: `ComparisonServiceImpl.checkProductUseCase1()` (Line 113-306) - **주요 변경사항**: - - API 호출 3회 → 4회 (명의이전 시점 소유자명 확인 추가) - - 검사일 이전일자 중 가장 마지막 명의이전 레코드 찾기 - - 소유자명 일치 여부 확인 로직 추가 + - 검사일 이전일자 조건 주석처리 → 검사종료일자 이전일자만 제한 + - 기간 범위 확인 조건 주석처리 → 제거됨 + - 비고 내용 간소화 + +### ✅ 내사종결 Case2 로직 (신규 추가) +- **구현 완료**: 신규 로직 추가 완료 +- **구현 위치**: `ComparisonServiceImpl.checkCloseProductUse()` (Line 329-515) +- **주요 특징**: + - 명의이전(30일 이내) 후 상품용인 경우 처리 + - CHG_YMD - 1일 기준 조회 + - 상세한 비고 정보 생성 ### ✅ 이첩 로직 - **구현 완료**: 이첩-1, 이첩-2 병합 로직 구현 완료 -- **구현 위치**: `ComparisonServiceImpl.checkTransfer()` \ No newline at end of file +- **구현 위치**: `ComparisonServiceImpl.checkTransferCase115Day()` (Line 531-648) \ No newline at end of file diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/controller/CarFfnlgTrgtController.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/controller/CarFfnlgTrgtController.java index 2bbabab..ad0c9c4 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/controller/CarFfnlgTrgtController.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/controller/CarFfnlgTrgtController.java @@ -4,6 +4,9 @@ import egovframework.constant.MessageConstants; import egovframework.constant.TilesConstants; import egovframework.util.ApiResponseUtil; import egovframework.util.SessionUtil; +import egovframework.util.excel.ExcelSheetData; +import egovframework.util.excel.SxssfExcelFile; +import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtModifiedDataVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService; @@ -21,6 +24,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -519,4 +523,36 @@ public class CarFfnlgTrgtController { } } } + + /** + * 과태료 대상 목록 엑셀 다운로드 + * + * @param paramVO 검색 조건을 담은 VO 객체 + * @param request HTTP 요청 객체 + * @param response HTTP 응답 객체 + */ + @PostMapping("/excel.do") + @Operation(summary = "과태료 대상 목록 엑셀 다운로드", description = "과태료 대상 목록을 엑셀 파일로 다운로드합니다.") + public void downloadExcel( + @ModelAttribute CarFfnlgTrgtVO paramVO, + HttpServletRequest request, + HttpServletResponse response) { + try { + log.debug("과태료 대상 목록 엑셀 다운로드 요청"); + + // 페이징 처리 없이 전체 데이터 조회 + paramVO.setPagingYn("N"); + + // 과태료 대상 목록 조회 + List excelList = service.selectListForExcel(paramVO); + + // 엑셀 파일 생성 및 다운로드 + String filename = "과태료대상목록_" + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx"; + new SxssfExcelFile(ExcelSheetData.of(excelList, CarFfnlgTrgtExcelVO.class, "과태료 대상 목록 " + excelList.size() + "건"), request, response, filename); + + log.debug("과태료 대상 목록 엑셀 다운로드 완료 - 파일명: {}, 건수: {}", filename, excelList.size()); + } catch (Exception e) { + log.error("엑셀 다운로드 중 오류 발생", e); + } + } } diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/mapper/CarFfnlgTrgtMapper.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/mapper/CarFfnlgTrgtMapper.java index 694f02e..4621777 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/mapper/CarFfnlgTrgtMapper.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/mapper/CarFfnlgTrgtMapper.java @@ -1,5 +1,6 @@ package go.kr.project.carInspectionPenalty.registration.mapper; +import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import org.apache.ibatis.annotations.Mapper; @@ -73,4 +74,11 @@ public interface CarFfnlgTrgtMapper { * @return 시군구명 */ String selectSggNmBySggCd(String sggCd); + + /** + * 과태료 대상 목록 엑셀 다운로드용 조회 + * @param vo 검색 조건 + * @return 엑셀 다운로드용 목록 + */ + List selectListForExcel(CarFfnlgTrgtVO vo); } diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/model/CarFfnlgTrgtExcelVO.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/model/CarFfnlgTrgtExcelVO.java new file mode 100644 index 0000000..c7fce89 --- /dev/null +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/model/CarFfnlgTrgtExcelVO.java @@ -0,0 +1,124 @@ +package go.kr.project.carInspectionPenalty.registration.model; + +import egovframework.util.excel.ExcelColumn; +import egovframework.util.excel.ExcelSheet; +import lombok.*; + +/** + * 과태료 대상 목록 엑셀 다운로드용 VO 클래스 + * + *

엑셀 다운로드 시 사용되는 전용 VO로 @ExcelColumn 어노테이션을 포함

+ *

엑셀 샘플 순서에 맞게 필드를 정렬하고, 필요한 컬럼만 헤더 설정

+ */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +@ExcelSheet(name = "과태료대상목록") +public class CarFfnlgTrgtExcelVO { + + /** 접수일자 */ + @ExcelColumn(headerName = "접수일자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String rcptYmd; + + /** 검사소코드 */ + @ExcelColumn(headerName = "검사소코드", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String inspstnCd; + + /** 검사일자 */ + @ExcelColumn(headerName = "검사일자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String inspYmd; + + /** 차량번호 */ + @ExcelColumn(headerName = "차량번호", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String vhclno; + + /** 소유자명 */ + @ExcelColumn(headerName = "소유자명", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String ownrNm; + + /** 주민등록번호 - 일단제외, 개인정보위험 */ + //@ExcelColumn(headerName = "주민등록번호", headerWidth = 20, align = ExcelColumn.Align.CENTER) + //private String rrno; + + /** 자동차명 */ + @ExcelColumn(headerName = "자동차명", headerWidth = 20, align = ExcelColumn.Align.LEFT) + private String carNm; + + /** 자동차종류 */ + @ExcelColumn(headerName = "자동차종류", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String carKnd; + + /** 자동차용도 */ + @ExcelColumn(headerName = "자동차용도", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String carUsg; + + /** 검사종료일자 */ + @ExcelColumn(headerName = "검사종료일자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String inspEndYmd; + + /** 일수 */ + @ExcelColumn(headerName = "일수", headerWidth = 10, align = ExcelColumn.Align.RIGHT) + private Long daycnt; + + /** 과태료금액 */ + @ExcelColumn(headerName = "과태료금액", headerWidth = 15, align = ExcelColumn.Align.RIGHT) + private Long ffnlgAmt; + + /** 최종등록일자 */ + @ExcelColumn(headerName = "최종등록일자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String lastRegYmd; + + /** 주소 */ + @ExcelColumn(headerName = "주소", headerWidth = 40, align = ExcelColumn.Align.LEFT) + private String addr; + + /** 유효기간만료일자 */ + @ExcelColumn(headerName = "유효기간만료일자", headerWidth = 18, align = ExcelColumn.Align.CENTER) + private String vldPrdExpryYmd; + + /** 매매상품 */ + @ExcelColumn(headerName = "매매상품", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String trdGds; + + /** 처리상태 */ + @ExcelColumn(headerName = "처리상태", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String taskPrcsSttsCdNm; + + /** 처리일자 */ + @ExcelColumn(headerName = "처리일자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String taskPrcsYmd; + + /** 비고 */ + @ExcelColumn(headerName = "비고", headerWidth = 30, align = ExcelColumn.Align.LEFT) + private String rmrk; + + /** 기본사항조회성명 */ + @ExcelColumn(headerName = "기본사항조회성명", headerWidth = 18, align = ExcelColumn.Align.CENTER) + private String carBscMttrInqFlnm; + + /** 기본사항조회시군구명 */ + @ExcelColumn(headerName = "기본사항조회시군구명", headerWidth = 20, align = ExcelColumn.Align.CENTER) + private String carBscMttrInqSggNm; + + /** 등록원부변경업무명 */ + @ExcelColumn(headerName = "등록원부변경업무명", headerWidth = 20, align = ExcelColumn.Align.CENTER) + private String carRegFrmbkChgTaskSeNm; + + /** 등록원부변경일자 */ + @ExcelColumn(headerName = "등록원부변경일자", headerWidth = 18, align = ExcelColumn.Align.CENTER) + private String carRegFrmbkChgYmd; + + /** 등록원부상세 */ + @ExcelColumn(headerName = "등록원부상세", headerWidth = 40, align = ExcelColumn.Align.LEFT) + private String carRegFrmbkDtl; + + /** 등록일시 */ + @ExcelColumn(headerName = "등록일시", headerWidth = 20, align = ExcelColumn.Align.CENTER) + private String regDt; + + /** 등록자 */ + @ExcelColumn(headerName = "등록자", headerWidth = 15, align = ExcelColumn.Align.CENTER) + private String rgtrNm; +} diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/CarFfnlgTrgtService.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/CarFfnlgTrgtService.java index ef286e0..ce7ce78 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/CarFfnlgTrgtService.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/CarFfnlgTrgtService.java @@ -1,5 +1,6 @@ package go.kr.project.carInspectionPenalty.registration.service; +import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtModifiedDataVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import org.springframework.web.multipart.MultipartFile; @@ -87,4 +88,11 @@ public interface CarFfnlgTrgtService { * @return 저장 건수 */ int saveCarFfnlgTrgts(CarFfnlgTrgtModifiedDataVO modifyData); + + /** + * 과태료 대상 목록 엑셀 다운로드용 조회 + * @param vo 검색 조건 + * @return 엑셀 다운로드용 목록 + */ + List selectListForExcel(CarFfnlgTrgtVO vo); } diff --git a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java index 2ec2968..5af8f6d 100644 --- a/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java +++ b/src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/CarFfnlgTrgtServiceImpl.java @@ -6,6 +6,7 @@ import egovframework.util.SessionUtil; import go.kr.project.api.service.ExternalVehicleApiService; import go.kr.project.carInspectionPenalty.registration.config.CarFfnlgTxtParseConfig; import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper; +import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtModifiedDataVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService; @@ -1072,4 +1073,14 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements return result; } + /** + * 과태료 대상 목록 엑셀 다운로드용 조회 + * @param vo 검색 조건 + * @return 엑셀 다운로드용 목록 + */ + @Override + public List selectListForExcel(CarFfnlgTrgtVO vo) { + log.debug("과태료 대상 목록 엑셀 다운로드용 조회 - 검색조건: {}", vo); + return mapper.selectListForExcel(vo); + } } diff --git a/src/main/resources/mybatis/mapper/carInspectionPenalty/history/VehicleApiHistoryMapper_maria.xml b/src/main/resources/mybatis/mapper/carInspectionPenalty/history/VehicleApiHistoryMapper_maria.xml index 6bc4516..deb81b0 100644 --- a/src/main/resources/mybatis/mapper/carInspectionPenalty/history/VehicleApiHistoryMapper_maria.xml +++ b/src/main/resources/mybatis/mapper/carInspectionPenalty/history/VehicleApiHistoryMapper_maria.xml @@ -131,7 +131,7 @@ AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId} - ORDER BY t.REG_DT DESC + ORDER BY t.CAR_BASS_MATTER_INQIRE_ID DESC LIMIT #{startIndex}, #{perPage} @@ -279,7 +279,7 @@ AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId} - ORDER BY t.REG_DT DESC + ORDER BY t.CAR_LEDGER_FRMBK_ID DESC LIMIT #{startIndex}, #{perPage} diff --git a/src/main/resources/mybatis/mapper/carInspectionPenalty/registration/CarFfnlgTrgtMapper_maria.xml b/src/main/resources/mybatis/mapper/carInspectionPenalty/registration/CarFfnlgTrgtMapper_maria.xml index 0961092..61da5a4 100644 --- a/src/main/resources/mybatis/mapper/carInspectionPenalty/registration/CarFfnlgTrgtMapper_maria.xml +++ b/src/main/resources/mybatis/mapper/carInspectionPenalty/registration/CarFfnlgTrgtMapper_maria.xml @@ -281,4 +281,46 @@ LIMIT 1 + + + diff --git a/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp b/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp index 6a3d3fd..3215652 100644 --- a/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp +++ b/src/main/webapp/WEB-INF/views/carInspectionPenalty/registration/list.jsp @@ -11,7 +11,12 @@
과태료 대상 목록
- + +
@@ -651,6 +656,35 @@ window.location.href = url; }); + // 엑셀 다운로드 버튼 클릭 + $("#excelDownloadBtn").on('click', function() { + // 검색조건 비교 및 확인 + if (!compareSearchConditions()) { + return; + } + + // Form 생성 및 제출 + var $form = $('
', { + method: 'POST', + action: '' + }); + + // 검색 조건 파라미터 추가 + setSearchCond(); + $.each(SEARCH_COND, function(key, value) { + if (value) { + $form.append($('', { + type: 'hidden', + name: key, + value: value + })); + } + }); + + // Form을 body에 추가하고 제출 후 제거 + $form.appendTo('body').submit().remove(); + }); + // 페이지당 건수 변경 $("#perPageSelect").on('change', function() { var perPage = parseInt($(this).val(), 10);