엑셀다운로드 추가, 로직 md 파일 재정의

main
박성영 2 weeks ago
parent 8e13313d57
commit e20aeba146

@ -16,13 +16,14 @@
| 일자 | 변경 내용 | 비고 | | 일자 | 변경 내용 | 비고 |
|------|----------|------| |------|----------|------|
| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 30일 이내 조건 추가 |
| 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 | | 2025-01-XX | 상품용 로직 완전 변경 | API 호출 4단계, 소유자명 일치 확인 추가 |
| 2025-01-XX | 문서 업데이트 완료 | 신규 로직 반영 및 코드 예시 추가 |
### 처리 규칙 ### 처리 규칙
> **중요**: 순서가 중요함! > **중요**: 순서가 중요함!
> - 1. 상품용 검증 (Case1) → 2. 내사종결 상품용 검증 (Case2) → 3. 이첩 검증
> - 조건에 걸리는 순간 다음 차량번호 비교로 진행 > - 조건에 걸리는 순간 다음 차량번호 비교로 진행
> - 각 비교 로직별로 개별 API 호출 수행 > - 각 비교 로직별로 개별 API 호출 수행
@ -30,9 +31,10 @@
## 비교 로직 상세 ## 비교 로직 상세
### 1. 상품용 검증 (2025년 신규 로직) ### 1. 상품용 검증 Case1 - 검사일 소유자가 상품용
**필요 API**: 자동차기본정보 (3회), 자동차등록원부(갑) (1회) **처리상태코드**: `02` (상품용)
**메서드**: `checkProductUseCase1()` (ComparisonServiceImpl.java:113~306)
#### API 호출 순서 #### API 호출 순서
@ -46,34 +48,110 @@
#### 비교 조건 (순차 실행) #### 비교 조건 (순차 실행)
```java ```java
// 조건 1: 소유자명에 '상품용' 포함 여부 // 조건 1: 소유자명에 '상품용' 포함 여부 (Line 138-142)
if (!api1Response.소유자명.contains("상품용")) { if (!step1Record.소유자명.contains("상품용")) {
return false; // 미충족 return null; // 미충족
} }
// 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 // 조건 2: 갑부 상세에서 조건에 맞는 명의이전 레코드 찾기 (Line 187-234)
LocalDate latestChgDate = null; LocalDate latestChgDate = null;
LedgerRecord targetRecord = null; NewLedgerResponse.Record targetRecord = null;
for (LedgerRecord record : 갑부상세List) { for (NewLedgerResponse.Record record : ledgerRecords) {
// 2-1. 명의이전 코드 확인 // 2-1. 명의이전 코드 확인 (Line 199)
if (!record.CHG_TASK_SE_CD.equals("11")) { if (!"11".equals(record.CHG_TASK_SE_CD)) {
continue; // 명의이전이 아니면 스킵 continue; // 명의이전이 아니면 스킵
} }
// 2-2. CHG_YMD <= 검사종료일자 // 2-2. CHG_YMD <= 검사종료일자 (Line 208-213)
if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사종료일자) { if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사종료일자) {
continue; // 검사종료일자 이후면 스킵 continue; // 검사종료일자 이후면 스킵
} }
// 일단 주석처리 2-3. CHG_YMD <= 검사일 (검사일 이전일자만) // 주석처리됨: CHG_YMD <= 검사일 조건 (Line 215-221)
/* // → 검사일 이후 데이터도 포함 (검사종료일자까지만 제한)
if (record.CHG_YMD > TB_CAR_FFNLG_TRGT.검사일) {
continue; // 검사일 이후면 스킵 // 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. 내사종결 상품용 검증 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-4. 가장 마지막 일자 찾기 // 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) { if (latestChgDate == null || record.CHG_YMD > latestChgDate) {
latestChgDate = record.CHG_YMD; latestChgDate = record.CHG_YMD;
targetRecord = record; targetRecord = record;
@ -81,55 +159,50 @@ for (LedgerRecord record : 갑부상세List) {
} }
if (targetRecord == null) { if (targetRecord == null) {
return false; // 조건에 맞는 레코드 없음 return null; // 조건에 맞는 레코드 없음
} }
// 조건 3: 명의이전 시점 소유자명 == 검사일 기준 소유자명 // 조건 3: 명의이전일자가 검사일의 30일 이내인지 확인 (Line 444-450)
if (!api4Response.소유자명.equals(api1Response.소유자명)) { long daysBetween = ChronoUnit.DAYS.between(latestChgDate, 검사일);
return false; // 소유자명 불일치 if (daysBetween < 0 || daysBetween > 30) {
return null; // 30일 초과
} }
// 일단 주석처리 조건 4: CHG_YMD가 유효기간만료일 ~ 검사종료일자 범위 내 // 조건 4: 명의이전 직전 시점(CHG_YMD-1일) 소유자명에 '상품용' 포함 (Line 476-482)
/* // Step 4 API: 부과일자 = CHG_YMD - 1일 (Line 457-464)
if (targetRecord.CHG_YMD >= TB_CAR_FFNLG_TRGT.유효기간만료일 if (step4Record.소유자명 == null || !step4Record.소유자명.contains("상품용")) {
&& targetRecord.CHG_YMD <= TB_CAR_FFNLG_TRGT.검사종료일자) { return null; // 명의이전 직전 소유자가 상품용이 아님
return true; // 상품용 조건 충족
} }
*/
return false; // 미충족 return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED; // 내사종결 조건 충족
``` ```
#### 로직 설명
이 로직은 **검사일 당시에는 상품용이 아니었지만, 명의이전 이전(30일 이내)에는 상품용이었던 경우**를 처리합니다:
1. 검사일 소유자가 상품용이 아님
2. 검사일 이전 30일 이내에 명의이전(코드 11) 발생
3. 명의이전 직전 소유자가 상품용
#### 결과 처리 #### 결과 처리
- **업무 처리 상태 코드**: `02` (상품용) - **업무 처리 상태 코드**: `04` (내사종결)
- **비고 컬럼 형식**: - **비고 컬럼 형식**: (Line 784-818)
``` ```
상품용 명의이전(25.9.3.) 이전소유자 상품용
22루2283
■ 검사일 기준 소유자정보 - 검사기간: 2024-08-01 - 2024-08-31
- 소유자명: {소유자명} - 검사일: 2024-08-15
- 차대번호: {차대번호} - 명의이전: 2024-09-03
- 상품용: 2024-09-03
■ 명의이전 시점 소유자정보
- 소유자명: {소유자명}
- 조회일자: {CHG_YMD}
■ 갑부 상세 (명의이전 이력)
- 변경일자: {CHG_YMD}
- 변경업무코드: 11
- 변경업무명: 명의이전
- 접수번호: {접수번호}
■ 비교 기간
- 유효기간만료일: {유효기간만료일}
- 검사종료일자: {검사종료일자}
- 판정: 명의이전일자가 기간 내 존재하고 검사일 소유자명과 일치
``` ```
--- ---
### 2. 이첩 검증 (이첩-1, 이첩-2 병합 로직) ### 3. 이첩 검증 (이첩-1, 이첩-2 병합 로직)
**처리상태코드**: `03` (이첩)
**메서드**: `checkTransferCase115Day()` (ComparisonServiceImpl.java:531~648)
**필요 API**: 자동차기본정보 **필요 API**: 자동차기본정보
@ -239,67 +312,111 @@ private boolean checkTransferCondition_LegalDongMismatch(
[차량번호 조회] [차량번호 조회]
┌─────────────────────────────────────────────────────┐ ┌────────────────────────────────────────────────────────────┐
│ 1. 상품용 검증 (4단계 API 호출) │ │ 1. 상품용 Case1 - 검사일 소유자가 상품용 │
├─────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────┤
│ Step 1: 자동차기본정보(차량번호, 검사일) │ │ Step 1: 자동차기본정보(차량번호, 검사일) │
│ → 소유자명에 "상품용" 포함? │ │ → 소유자명에 "상품용" 포함? │
│ │ │ │ │ │
│ ├─ (No) ──────────────────────────┐ │ │ ├─ (No) ──────────────────────────────────┐ │
│ │ │ │ │ │ │ │
│ └─ (Yes) │ │ │ └─ (Yes) │ │
│ ▼ │ │ │ ▼ │ │
│ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │ │ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │
│ → 현재 소유자 정보 │ │ │ → 현재 소유자 정보 │ │
│ │ │ │ │ ▼ │ │
│ ▼ │ │ │ Step 3: 자동차등록원부(갑) 조회 │ │
│ Step 3: 자동차등록원부(갑) 조회 │ │ │ → 갑부 상세 List │ │
│ → 갑부 상세 List │ │ │ ▼ │ │
│ │ │ │ │ 검사종료일자 이전 가장 마지막 명의이전(11) │ │
│ ▼ │ │ │ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │
│ 검사일 이전 가장 마지막 명의이전(11) │ │ │ │ │ │
│ 레코드 찾기 (CHG_YMD <= 검사종료일자) │ │ │ ├─ (없음) ─────────────────────────────┐ │ │
│ │ │ │ │ │ │ │ │
│ ├─ (없음) ─────────────────────┐ │ │ │ └─ (발견) │ │ │
│ │ │ │ │ │ ▼ │ │ │
│ └─ (발견) │ │ │ │ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │
│ ▼ │ │ │ │ → 명의이전 시점 소유자명 │ │ │
│ Step 4: 자동차기본정보(차대번호, CHG_YMD) │ │ │ │ │ │ │ │
│ → 명의이전 시점 소유자명 │ │ │ │ ▼ │ │ │
│ │ │ │ │ │ 소유자명 일치? │ │ │
│ ▼ │ │ │ │ │ │ │ │
│ 소유자명 일치? && 기간 내 존재? │ │ │ │ ├─ (No) ─────────────────────────┐ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ ├─ (No) ───────────────────┐ │ │ │ │ └─ (Yes) │ │ │ │
│ │ │ │ │ │ │ ▼ │ │ │ │
│ └─ (Yes) │ │ │ │ │ [상품용(02) 처리 완료] │ │ │ │
│ ▼ │ │ │ │ │ │ │ │ │ │
│ [상품용 처리 완료] │ │ │ │ │ └──> [다음 차량] <────────────────┴─────┴───┘ │
│ │ │ │ │ │ └────────────────────────────────────────────────────────────┘
│ └──> [다음 차량] <──────────┴───┴───┘ │
└─────────────────────────────────────────────────────┘
│ (조건 미충족) │ (조건 미충족)
┌─────────────────────────────────────────────────────┐ ┌────────────────────────────────────────────────────────────┐
│ 2. 이첩 검증 (이첩-1, 이첩-2) │ │ 2. 내사종결 Case2 - 명의이전 후 상품용 │
├─────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────┤
│ DAYCNT 확인 │ │ Step 1: 자동차기본정보(차량번호, 검사일) │
│ │ │ │ → 소유자명에 "상품용" 미포함? │
│ ├─ (> 115) ──> [이첩-2: 부과기준일 = 검사종료일+115일] │ │ │ │
│ │ │ │ ├─ (No) ──────────────────────────────────┐ │
│ └─ (<= 115) ─> [이첩-1: 부과기준일 = 검사일] │ │ │ │ │
│ │ │ │ └─ (Yes) │ │
│ ▼ │ │ ▼ │ │
│ [자동차기본정보 API 호출] │ │ Step 2: 자동차기본정보(차대번호, 오늘일자) │ │
│ │ │ │ → 현재 소유자 정보 │ │
│ ▼ │ │ ▼ │ │
│ [법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리] │ │ 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만 호출 1. **API 호출 순서 준수**: 각 검증 단계별로 필요한 API만 호출
2. **조건 우선순위**: 상품용 > 이첩 순서로 검증 2. **조건 우선순위**: 상품용 Case1 > 내사종결 Case2 > 이첩 순서로 검증
3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동 3. **조기 종료**: 조건 충족 시 즉시 다음 차량으로 이동
4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록 4. **비고 컬럼**: 각 조건별 정해진 형식으로 기록
5. **날짜 형식 지원**: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 (`DateUtil` 사용) 5. **날짜 형식 지원**: yyyyMMdd, yyyy-MM-dd 두 형식 모두 자동 처리 (`DateUtil` 사용)
### 상품용 검증 주의사항 ### 상품용 Case1 주의사항
1. **4단계 API 호출 필수**: Step 1~4 모두 순차적으로 실행 1. **4단계 API 호출 필수**: Step 1~4 모두 순차적으로 실행
2. **가장 마지막 명의이전 레코드**: 검사일 이전일자 중 CHG_YMD가 가장 큰 값 선택 2. **가장 마지막 명의이전 레코드**: 검사종료 이전일자 중 CHG_YMD가 가장 큰 값 선택
3. **소유자명 정확한 일치**: Step1 소유자명 == Step4 소유자명 (완전 일치) 3. **소유자명 정확한 일치**: Step1 소유자명 == Step4 소유자명 (완전 일치)
4. **기간 범위 확인**: CHG_YMD가 유효기간만료일 ~ 검사종료일자 사이에 존재해야 함 4. **주석처리된 조건**:
- ~~검사일 이전일자 제한~~ → 검사종료일자 이전일자만 제한
- ~~기간 범위 확인~~ → 제거됨
5. **로그 상세 기록**: 각 단계별 결과를 상세히 로깅하여 디버깅 지원 5. **로그 상세 기록**: 각 단계별 결과를 상세히 로깅하여 디버깅 지원
### 내사종결 Case2 주의사항
1. **검사일 소유자 확인**: 검사일 소유자가 상품용이 아니어야 함 (Case1과 구분)
2. **30일 이내 조건**: 명의이전일자가 검사일의 30일 이내여야 함
3. **CHG_YMD-1일 조회**: Step 4에서 명의이전 직전 시점(CHG_YMD - 1일) 조회
4. **검사일 기준**: 갑부 레코드는 검사일 이전일자 중 가장 마지막 일자 선택
5. **비고 형식**: 명의이전 날짜, 차량번호, 검사기간 등 상세 정보 포함
### 이첩 검증 주의사항 ### 이첩 검증 주의사항
1. **법정동코드 길이 검증**: 최소 4자리 이상 필요 1. **법정동코드 길이 검증**: 최소 4자리 이상 필요
2. **DAYCNT 기준 명확**: > 115 (초과), <= 115 (이하) 구분 2. **DAYCNT 기준 명확**: > 115 (초과), <= 115 (이하) 구분
@ -328,104 +454,69 @@ private boolean checkTransferCondition_LegalDongMismatch(
## 내가 text 로 정리한 내용 ## 요약 정리
### 1. 상품용 (2025년 신규 로직)
#### 필요한 API 정보
1. 자동차기본정보 api 호출 [차량번호, 부과일자:검사일]
→ response.차대번호, response.소유자명
2. 자동차기본정보 api 호출 [1.response.차대번호, 부과일자:오늘일자] ### 비교 로직 실행 순서 (executeComparison)
→ 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 병합로직
#### 부과기준일 결정
부과기준일 구하는 부분만 다르고 나머지 법정동코드 비교로직은 동일
```java ```java
if (TB_CAR_FFNLG_TRGT.DAYCNT > 115) { // ComparisonServiceImpl.java Line 50-85
부과기준일 = TB_CAR_FFNLG_TRGT.검사종료일자 + 115일 public String executeComparison(CarFfnlgTrgtVO existingData) {
// 이첩-2 // 1. 상품용 Case1 - 검사일 소유자가 상품용
비고 = "전라남도 순천시 / 김정대, 115일 도래지, [법정동코드 및 법정동명]" String result1 = checkProductUseCase1(existingData);
} else { if (result1 != null) return result1; // "02"
부과기준일 = TB_CAR_FFNLG_TRGT.검사일자
// 이첩-1 // 2. 내사종결 Case2 - 명의이전 후 상품용
비고 = "서울시 용산구/ 이경호, 검사일사용본거지, [검사대상, 사용자 조직코드 앞 4자리 및 법정동명]" 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자리 비교 | 구분 | Case1 (상품용) | Case2 (내사종결) |
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4); |------|---------------|-----------------|
String userOrgCd = userInfo.getOrgCd(); | 검사일 소유자 | 상품용 포함 | 상품용 미포함 |
String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd; | 갑부 레코드 기준 | CHG_YMD <= 검사종료일자 | CHG_YMD <= 검사일 |
| 추가 조건 | - | 명의이전일자가 검사일의 30일 이내 |
if (legalDong4.equals(userOrg4)) { | Step 4 조회일 | CHG_YMD | CHG_YMD - 1일 |
// 법정동코드 일치 - 이첩 대상 아님 | Step 4 확인사항 | 소유자명 일치 | 소유자명에 "상품용" 포함 |
return false; | 처리상태코드 | 02 | 04 |
}
// 법정동코드 불일치 - 이첩 대상
return true;
```
--- ---
## 구현 완료 상태 ## 구현 완료 상태
### ✅ 상품용 로직 ### ✅ 상품용 Case1 로직
- **구현 완료**: 신규 로직으로 구현 완료 - **구현 완료**: 신규 로직으로 구현 완료
- **구현 위치**: `ComparisonServiceImpl.checkProductUse()` - **구현 위치**: `ComparisonServiceImpl.checkProductUseCase1()` (Line 113-306)
- **주요 변경사항**: - **주요 변경사항**:
- API 호출 3회 → 4회 (명의이전 시점 소유자명 확인 추가) - 검사일 이전일자 조건 주석처리 → 검사종료일자 이전일자만 제한
- 검사일 이전일자 중 가장 마지막 명의이전 레코드 찾기 - 기간 범위 확인 조건 주석처리 → 제거됨
- 소유자명 일치 여부 확인 로직 추가 - 비고 내용 간소화
### ✅ 내사종결 Case2 로직 (신규 추가)
- **구현 완료**: 신규 로직 추가 완료
- **구현 위치**: `ComparisonServiceImpl.checkCloseProductUse()` (Line 329-515)
- **주요 특징**:
- 명의이전(30일 이내) 후 상품용인 경우 처리
- CHG_YMD - 1일 기준 조회
- 상세한 비고 정보 생성
### ✅ 이첩 로직 ### ✅ 이첩 로직
- **구현 완료**: 이첩-1, 이첩-2 병합 로직 구현 완료 - **구현 완료**: 이첩-1, 이첩-2 병합 로직 구현 완료
- **구현 위치**: `ComparisonServiceImpl.checkTransfer()` - **구현 위치**: `ComparisonServiceImpl.checkTransferCase115Day()` (Line 531-648)

@ -4,6 +4,9 @@ import egovframework.constant.MessageConstants;
import egovframework.constant.TilesConstants; import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil; import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil; 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.CarFfnlgTrgtModifiedDataVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService; 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.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; 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<CarFfnlgTrgtExcelVO> 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);
}
}
} }

@ -1,5 +1,6 @@
package go.kr.project.carInspectionPenalty.registration.mapper; package go.kr.project.carInspectionPenalty.registration.mapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtExcelVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@ -73,4 +74,11 @@ public interface CarFfnlgTrgtMapper {
* @return * @return
*/ */
String selectSggNmBySggCd(String sggCd); String selectSggNmBySggCd(String sggCd);
/**
*
* @param vo
* @return
*/
List<CarFfnlgTrgtExcelVO> selectListForExcel(CarFfnlgTrgtVO vo);
} }

@ -0,0 +1,124 @@
package go.kr.project.carInspectionPenalty.registration.model;
import egovframework.util.excel.ExcelColumn;
import egovframework.util.excel.ExcelSheet;
import lombok.*;
/**
* VO
*
* <p> VO @ExcelColumn </p>
* <p> , </p>
*/
@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;
}

@ -1,5 +1,6 @@
package go.kr.project.carInspectionPenalty.registration.service; 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.CarFfnlgTrgtModifiedDataVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -87,4 +88,11 @@ public interface CarFfnlgTrgtService {
* @return * @return
*/ */
int saveCarFfnlgTrgts(CarFfnlgTrgtModifiedDataVO modifyData); int saveCarFfnlgTrgts(CarFfnlgTrgtModifiedDataVO modifyData);
/**
*
* @param vo
* @return
*/
List<CarFfnlgTrgtExcelVO> selectListForExcel(CarFfnlgTrgtVO vo);
} }

@ -6,6 +6,7 @@ import egovframework.util.SessionUtil;
import go.kr.project.api.service.ExternalVehicleApiService; import go.kr.project.api.service.ExternalVehicleApiService;
import go.kr.project.carInspectionPenalty.registration.config.CarFfnlgTxtParseConfig; import go.kr.project.carInspectionPenalty.registration.config.CarFfnlgTxtParseConfig;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper; 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.CarFfnlgTrgtModifiedDataVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO; import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService; import go.kr.project.carInspectionPenalty.registration.service.CarFfnlgTrgtService;
@ -1072,4 +1073,14 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
return result; return result;
} }
/**
*
* @param vo
* @return
*/
@Override
public List<CarFfnlgTrgtExcelVO> selectListForExcel(CarFfnlgTrgtVO vo) {
log.debug("과태료 대상 목록 엑셀 다운로드용 조회 - 검색조건: {}", vo);
return mapper.selectListForExcel(vo);
}
} }

@ -131,7 +131,7 @@
<if test='searchCarFfnlgTrgtId != null and searchCarFfnlgTrgtId != ""'> <if test='searchCarFfnlgTrgtId != null and searchCarFfnlgTrgtId != ""'>
AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId} AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId}
</if> </if>
ORDER BY t.REG_DT DESC ORDER BY t.CAR_BASS_MATTER_INQIRE_ID DESC
<if test='pagingYn == "Y"'> <if test='pagingYn == "Y"'>
LIMIT #{startIndex}, #{perPage} LIMIT #{startIndex}, #{perPage}
</if> </if>
@ -279,7 +279,7 @@
<if test='searchCarFfnlgTrgtId != null and searchCarFfnlgTrgtId != ""'> <if test='searchCarFfnlgTrgtId != null and searchCarFfnlgTrgtId != ""'>
AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId} AND t.CAR_FFNLG_TRGT_ID = #{searchCarFfnlgTrgtId}
</if> </if>
ORDER BY t.REG_DT DESC ORDER BY t.CAR_LEDGER_FRMBK_ID DESC
<if test='pagingYn == "Y"'> <if test='pagingYn == "Y"'>
LIMIT #{startIndex}, #{perPage} LIMIT #{startIndex}, #{perPage}
</if> </if>

@ -281,4 +281,46 @@
LIMIT 1 LIMIT 1
</select> </select>
<!-- 과태료 대상 목록 엑셀 다운로드용 조회 -->
<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.INSPSTN_CD AS inspstnCd,
DATE_FORMAT(STR_TO_DATE(t.INSP_YMD, '%Y%m%d'), '%Y-%m-%d') 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.DAYCNT AS daycnt,
t.FFNLG_AMT AS ffnlgAmt,
DATE_FORMAT(STR_TO_DATE(t.LAST_REG_YMD, '%Y%m%d'), '%Y-%m-%d') 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.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.RMRK AS rmrk,
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_DTL AS carRegFrmbkDtl,
DATE_FORMAT(t.REG_DT, '%Y-%m-%d %H:%i:%s') AS regDt,
u.USER_NM AS rgtrNm
FROM tb_car_ffnlg_trgt t
LEFT JOIN tb_user u ON t.RGTR = u.USER_ID
LEFT JOIN (
SELECT CD_ID, CD_NM
FROM tb_cd_detail
WHERE CD_GROUP_ID = 'TASK_PRCS_STTS_CD'
AND USE_YN = 'Y'
) cd ON t.TASK_PRCS_STTS_CD = cd.CD_ID
WHERE t.DEL_DT IS NULL
<include refid="searchCondition"/>
ORDER BY t.REG_DT DESC
</select>
</mapper> </mapper>

@ -11,7 +11,12 @@
<section id="section5"> <section id="section5">
<div class="sub_title">과태료 대상 목록</div> <div class="sub_title">과태료 대상 목록</div>
<button type="button" id="registerBtn" class="newbtn bg1">TXT 등록</button> <button type="button" id="registerBtn" class="newbtn bg1">TXT 등록</button>
<button type="button" id="downloadBtn" class="newbtn bg3">TXT 목록 다운로드</button> <button type="button" id="downloadBtn" class="newbtn bg3 iconz">
<span class="mdi mdi-file-document"></span>TXT 다운로드
</button>
<button type="button" id="excelDownloadBtn" class="newbtn bg3 iconz">
<span class="mdi mdi-microsoft-excel"></span>엑셀 다운로드
</button>
</section> </section>
</div> </div>
</section> </section>
@ -651,6 +656,35 @@
window.location.href = url; window.location.href = url;
}); });
// 엑셀 다운로드 버튼 클릭
$("#excelDownloadBtn").on('click', function() {
// 검색조건 비교 및 확인
if (!compareSearchConditions()) {
return;
}
// Form 생성 및 제출
var $form = $('<form>', {
method: 'POST',
action: '<c:url value="/carInspectionPenalty/registration/excel.do"/>'
});
// 검색 조건 파라미터 추가
setSearchCond();
$.each(SEARCH_COND, function(key, value) {
if (value) {
$form.append($('<input>', {
type: 'hidden',
name: key,
value: value
}));
}
});
// Form을 body에 추가하고 제출 후 제거
$form.appendTo('body').submit().remove();
});
// 페이지당 건수 변경 // 페이지당 건수 변경
$("#perPageSelect").on('change', function() { $("#perPageSelect").on('change', function() {
var perPage = parseInt($(this).val(), 10); var perPage = parseInt($(this).val(), 10);

Loading…
Cancel
Save