Compare commits

...

6 Commits

Author SHA1 Message Date
박성영 24a7af08b6 refactor: `RCPT_YMD` 컬럼의 날짜 포맷 변환 제거
- **XML 매핑 로직 단순화**
  - `RCPT_YMD` 컬럼: 불필요한 `DATE_FORMAT` 및 `STR_TO_DATE` 함수 제거.
  - DB 원본 값 그대로 활용하도록 수정.
13 hours ago
박성영 81fc0f2654 refactor: 날짜 포맷 변환 제거로 쿼리 간소화
- **XML 매핑 로직 변경**
  - `TASK_PRCS_YMD`, `CAR_REG_FRMBK_CHG_YMD` 컬럼: 불필요한 `DATE_FORMAT` 및 `STR_TO_DATE` 함수 제거.
  - DB에서 제공하는 원본 데이터 그대로 활용하도록 수정.
14 hours ago
박성영 f548cc0458 ### refactor: Excel VO 일수 데이터 타입 수정
- **`CarFfnlgTrgtExcelVO` 클래스 수정**
  - `daycnt` 필드 자료형을 `Long`에서 **`String`**으로 변경.
  - 변경된 자료형에 맞춰 코드 내부 데이터 처리 로직 대응 가능.

- **Excel 매핑 영향**
  - Excel 데이터 읽기/쓰기 과정에서 String 자료형 사용을 반영하도록 수정 필요.
  - **Excel 매핑 어노테이션 유지는 동일**: `@ExcelColumn`.

- **기타**
  - 가독성을 위해 주석 보완 가능성 탐색.
14 hours ago
박성영 293933e0ba ### feat: `Step 0` 및 `Step 1` 공통 로직 도입과 검사 체커 로직 개선
- **공통 로직 추가**
  - **`Step 0`, `Step 1`**: `ComparisonOmServiceImpl` 및 `ComparisonServiceImpl`에 공통 API 호출 로직 추가.
    - `step0Response`(차량번호), `step1Response`(차대번호) 생성 및 유효성 검증 로직 추가.
    - 모든 Checker에서 해당 공통 데이터를 활용 가능하도록 수정.

- **체커 순서 변경 및 메서드 개선**
  - [**미필**] 이첩 순서 변경: 기존 3번 → 2번으로 조정, 명의이전 확인 순서 조정.
  - [**지연**] 이첩 순서 변경: 기존 6번 → 4번으로 조정.
  - 기존 Checker 메서드에 공통화된 **`step0Response`, `step1Response`** 전달하도록 메서드 시그니처 업데이트.
  - 중복된 API 호출 제거 및 공통 데이터 활용으로 효율성 증가.

- **불필요 로직 제거 및 코드 정리**
  - 각 체커별 중복된 `Step 0`, `Step 1` API 호출 삭제.
  - 공통 응답 데이터 재활용.
  - 주석 보완 및 처리 흐름 가독성 강화.

- **DB 업데이트 관련 로직 명확화**
  - `이첩` 관련 처리 코드(시군구명, 법정동명) 구현 방식 및 String 결과 형식 문서화.

- **기타**
  - 문서 수정: `docs/자동차과태료_비교로직_정리-[미필/지연].md` 최종 수정일 업데이트.
  - 체커 순서 변경에 따른 처리 로직 및 데이터 흐름도 재정리.
14 hours ago
박성영 ca004dce5b ### feat: `TX_ID` 기반 인덱스 추가로 조회 성능 최적화
- **인덱스 추가**
  - 테이블 `tb_car_bass_matter_inqire`에 `TX_ID` 기반 인덱스(`idx_car_bass_matter_inqire_tx_id`) 생성.
  - 테이블 `tb_car_ledger_frmbk`에 `TX_ID` 기반 인덱스(`idx_car_ledger_frmbk_tx_id`) 생성.

- **DB 조회 성능 개선**
  - `TX_ID`를 활용한 조회 시 성능을 최적화하기 위한 인덱스 추가.

- **기타**
  - 각 테이블 정의를 유지하면서 최소 변경으로 성능 영향도 개선 노력.
15 hours ago
박성영 8259bd8d46 ### feat: MariaDB 성능 최적화 가이드 추가 및 스레드 풀/Connection Pool 설정 개선
- **새로운 MariaDB 성능 최적화 가이드 작성**
  - `docs/MariaDB 성능 최적화 가이드.md` 추가:
    - 최적화 목적: 처리 시간 **14~15분 → 8~10분** 단축.
    - MariaDB 설정(`my.cnf`) 및 애플리케이션 설정(`application-prd.yml`) 최적화 방안 제공.
    - 주요 설정 내용:
      - **Connection Pool**: 최대 풀 크기 40 → 35, 최소 유휴 커넥션 10 → 15로 변경.
      - **MariaDB 배치 최적화**: `rewriteBatchedStatements`, `useServerPrepStmts` 활성화.
      - **InnoDB 버퍼**: **3GB** 설정 (16GB 메모리 기준).
      - **I/O 스레드 최적화**: 쓰기/읽기 스레드 수 각각 4개로 설정.
    - 설정 백업/적용/롤백 방법 및 성능 모니터링 방법 자세히 설명.
    - 참고로 추가 최적화(인덱스, SSD, 슬로우 쿼리) 전략 포함.

- **스레드 풀 크기 최적화**
  - **I/O 작업 스레드 풀 크기 변경**:
    - `CarFfnlgTrgtIncmpServiceImpl` 및 `CarFfnlgTrgtServiceImpl`:
      - 4코어 환경 기준 스레드 풀 크기 **CPU 코어 수 × 4**로 변경(8 → 16).
      - 메모리 안정성 및 병렬 처리 성능 최적화.

- **애플리케이션 Connection Pool 설정 수정**
  - `application-prd.yml` 수정:
    - 최대 풀 크기: **40 → 35** (스레드 풀 크기에 맞춰 조정).
    - 최소 유휴 커넥션: **10 → 15**.
    - Batch 성능 향상을 위해 MariaDB 배치 최적화 설정 추가:
      - `rewriteBatchedStatements`, `useServerPrepStmts`, `cachePrepStmts`, `prepStmtCacheSize`, `prepStmtCacheSqlLimit`.

- **기타**
  - 모든 설정이 안정성 최우선을 목표로 작성.
  - 로직 최적화 및 설정 변경이 기존 환경에 미치는 영향을 최소화하기 위해 적용 방법 세부화.
  - 문서 및 주석 보완으로 설정 이해도를 향상.
15 hours ago

@ -106,3 +106,6 @@ create table tb_car_bass_matter_inqire
)
comment '자동차 기본 사항 조회';
create index idx_car_bass_matter_inqire_tx_id
on tb_car_bass_matter_inqire (TX_ID);

@ -88,3 +88,6 @@ create table tb_car_ledger_frmbk
)
comment '자동차 등록 원부 갑';
create index idx_car_ledger_frmbk_tx_id
on tb_car_ledger_frmbk (TX_ID);

@ -0,0 +1,356 @@
# MariaDB 성능 최적화 가이드 (안정성 최우선)
## 목적
740건 처리 시간을 14~15분 → **8~10분**으로 단축 (안정성 최우선)
---
## 1. application-prd.yml 수정 (완료)
### 수정 내용
- ✅ Connection Pool 크기 증가: 40 → 50
- ✅ Minimum Idle 증가: 10 → 20
- ✅ MariaDB 배치 최적화 설정 추가 (`rewriteBatchedStatements`, `useServerPrepStmts` 등)
### 적용 방법
```bash
# 애플리케이션 재시작
sudo systemctl restart vips
```
---
## 2. MariaDB 서버 설정 최적화 (필수)
### 설정 파일 위치
- **실서버 경로**: `/etc/my.cnf.d/` (CentOS/RHEL 계열)
- **주요 파일**: `mariadb-server.cnf`
### 설정 내용
#### 방법 1: 기존 mariadb-server.cnf 파일 백업 후 수정
```bash
# 1. 백업
sudo cp /etc/my.cnf.d/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf.backup.$(date +%Y%m%d)
# 2. 파일 편집
sudo vi /etc/my.cnf.d/mariadb-server.cnf
# [mysqld] 섹션 아래에 추가할 설정 붙여넣기
```
#### 방법 2: 별도 설정 파일 생성 (권장)
```bash
# 1. 성능 최적화 전용 설정 파일 생성
sudo vi /etc/my.cnf.d/99-performance.cnf
# 아래 전체 설정 내용을 복사하여 붙여넣기
```
#### 추가할 설정 내용 (16GB 메모리 기준, 안정성 최우선)
```ini
[mysqld]
# ==========================================
# 트랜잭션 안정성 설정 (안정성 최우선!)
# ==========================================
# 트랜잭션 로그 플러시 설정
# 1: 매 트랜잭션마다 플러시 (기본값, 가장 안전 - 데이터 손실 없음)
# 운영 환경에서는 안정성이 최우선이므로 기본값 유지
innodb_flush_log_at_trx_commit = 1
# ==========================================
# 연결 설정
# ==========================================
# 최대 동시 연결 수 증가
max_connections = 200
# ==========================================
# 버퍼 및 캐시 설정 (16GB 메모리 기준)
# ==========================================
# InnoDB 버퍼 풀 크기
# 16GB 메모리 기준: 16GB × 0.2 = 3.2GB → 3G (안전한 값)
# OS(2GB) + 애플리케이션(10GB) + 기타(1GB) + MariaDB(3GB) = 16GB
# 주의: 너무 크게 설정하면 swap 발생으로 성능 저하
innodb_buffer_pool_size = 3G
# 로그 파일 크기 (쓰기 성능 향상)
innodb_log_file_size = 256M
innodb_log_buffer_size = 32M
# ==========================================
# I/O 성능 최적화
# ==========================================
# 쓰기 I/O 스레드 수 (4코어 기준 적절한 값)
innodb_write_io_threads = 4
# 읽기 I/O 스레드 수 (4코어 기준 적절한 값)
innodb_read_io_threads = 4
# 플러시 방법 (디스크 I/O 최적화)
# O_DIRECT: 운영체제 캐시를 우회하여 직접 디스크에 쓰기
innodb_flush_method = O_DIRECT
# ==========================================
# 배치 처리 최적화
# ==========================================
# 배치 삽입 버퍼 크기
bulk_insert_buffer_size = 32M
# ==========================================
# 쿼리 캐시 (MariaDB 10.5 이상은 제거됨)
# ==========================================
# query_cache_type = 0
# query_cache_size = 0
```
### 3. 설정 적용 및 확인
#### MariaDB 재시작 (설정 검증은 재시작으로 확인)
```bash
# MariaDB 재시작
sudo systemctl restart mariadb
# 상태 확인
sudo systemctl status mariadb
```
#### 설정 확인
```sql
-- MariaDB 접속
mysql -u root -p
-- 설정 확인
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_log_file_size';
SHOW VARIABLES LIKE 'innodb_write_io_threads';
```
---
## 4. 성능 측정
### 적용 전후 비교
```bash
# 처리 시간 측정
# 적용 전: 14~15분
# 적용 후: 8~10분 (예상, 안정성 최우선 설정)
```
### 모니터링
```sql
-- 현재 연결 수 확인
SHOW STATUS LIKE 'Threads_connected';
-- 최대 연결 수 사용 현황
SHOW STATUS LIKE 'Max_used_connections';
-- InnoDB 상태 확인
SHOW ENGINE INNODB STATUS\G
```
---
## 5. 주의사항
### 안정성 최우선 설정 (운영 환경)
- **innodb_flush_log_at_trx_commit = 1**
- ✅ **가장 안전**: 시스템 크래시 시에도 데이터 손실 없음
- ⚠️ 성능: 기본 설정이지만 application-prd.yml 최적화로 충분한 성능 개선
- 운영 환경에서는 안정성이 최우선이므로 반드시 1로 유지
### innodb_buffer_pool_size 설정 (16GB 메모리 기준)
- **현재 설정: 3G (16GB 메모리의 약 20%)**
- **메모리 배분 계획**:
- OS: 약 2GB
- 애플리케이션(Java): 약 10GB
- MariaDB: 3GB
- 기타 프로세스: 약 1GB
- **⚠️ 주의**:
- 3G보다 크게 설정하면 swap 발생으로 오히려 성능 저하 가능
- 메모리 사용량 모니터링 후 필요시 점진적 증가 (최대 4G까지)
- **모니터링 필수**: `free -h` 명령어로 swap 사용량 확인
### 성능 vs 안정성 비교 (참고용)
| innodb_flush_log_at_trx_commit | 성능 | 안정성 | 적용 여부 |
|--------------------------------|------|--------|-----------|
| `1` (현재 설정) | 보통 | ✅ 최고 (데이터 손실 없음) | ✅ 적용 |
| `2` (성능 우선) | ⚡ 빠름 | ⚠️ 크래시 시 최대 1초 손실 | ❌ 미적용 |
| `0` (최고 성능) | ⚡⚡ 매우 빠름 | ❌ 크래시 시 최대 1초 손실 | ❌ 미적용 |
### 롤백 방법
#### 방법 1을 사용한 경우 (mariadb-server.cnf 수정)
```bash
# 백업 파일로 원복
sudo cp /etc/my.cnf.d/mariadb-server.cnf.backup.YYYYMMDD /etc/my.cnf.d/mariadb-server.cnf
sudo systemctl restart mariadb
```
#### 방법 2를 사용한 경우 (99-performance.cnf 생성)
```bash
# 생성한 파일 삭제
sudo rm /etc/my.cnf.d/99-performance.cnf
sudo systemctl restart mariadb
```
---
## 6. 추가 최적화 (선택)
### 인덱스 확인
```sql
-- 자주 사용하는 컬럼에 인덱스 존재 확인
SHOW INDEX FROM TB_CAR_FFNLG_TRGT;
SHOW INDEX FROM TB_CAR_FFNLG_TRGT_INCMP;
```
### 슬로우 쿼리 로그 활성화 (선택)
```ini
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow-query.log
long_query_time = 2
```
**참고:** 로그 파일 위치는 MariaDB 버전 및 배포판에 따라 다를 수 있습니다.
- CentOS/RHEL: `/var/lib/mysql/` 또는 `/var/log/mariadb/`
- 확인 방법: `SHOW VARIABLES LIKE 'datadir';`
---
## 7. 체크리스트
### 적용 전 확인
- [ ] MariaDB 설정 파일 백업 완료
- [ ] 서버 메모리 용량 확인 (16GB 기준 설정)
- [ ] 현재 메모리 사용량 확인 (`free -h`)
- [ ] 운영 시간대 피해 작업 (야간 작업 권장)
### 적용 후 확인
- [ ] MariaDB 정상 재시작 확인
- [ ] 애플리케이션 정상 동작 확인
- [ ] 처리 시간 측정 (740건 기준)
- [ ] CPU, 메모리 사용률 모니터링
- [ ] 에러 로그 확인 (`/var/log/mariadb/mariadb.log` 또는 `journalctl -u mariadb`)
---
## 8. 문제 해결
### MariaDB 재시작 실패 시
```bash
# 에러 로그 확인 (방법 1: 로그 파일)
sudo tail -100 /var/log/mariadb/mariadb.log
# 에러 로그 확인 (방법 2: systemd 저널)
sudo journalctl -u mariadb -n 100
# 설정 파일 복구 (방법 1 사용 시)
sudo cp /etc/my.cnf.d/mariadb-server.cnf.backup.YYYYMMDD /etc/my.cnf.d/mariadb-server.cnf
sudo systemctl restart mariadb
# 설정 파일 복구 (방법 2 사용 시)
sudo rm /etc/my.cnf.d/99-performance.cnf
sudo systemctl restart mariadb
```
### 성능 개선 효과가 없을 때
1. MariaDB 설정이 실제로 적용되었는지 확인
2. Connection Pool 크기 충분한지 확인
3. 네트워크 병목 확인 (DB 서버와 애플리케이션 서버 간)
4. 디스크 I/O 병목 확인 (`iostat -x 1`)
---
## 9. 예상 성능 개선 효과 (안정성 최우선 설정)
| 항목 | 적용 전 | 적용 후 | 개선율 |
|------|---------|---------|--------|
| **740건 처리 시간** | 14~15분 | **8~10분** | 약 30~40% |
| **CPU 사용률** | 25~31% | 50~70% | - |
| **DB 커넥션 대기** | 있음 | 최소화 | - |
| **데이터 안정성** | 안전 | **안전 (데이터 손실 없음)** | - |
### 성능 개선 원인
1. ✅ **스레드 풀 최적화** (24개 → 16개): 4코어 16GB 환경에 최적화, 메모리 안정성 확보
2. ✅ **Connection Pool 증가** (40 → 35): 스레드 수에 맞춰 최적화, DB 연결 대기 시간 감소
3. ✅ **rewriteBatchedStatements**: 배치 쿼리 최적화
4. ✅ **PreparedStatement 캐싱**: 쿼리 재사용으로 파싱 시간 절약
5. ✅ **InnoDB 버퍼 풀 증가** (기본 → 3G): DB 캐시 성능 향상
6. ✅ **I/O 스레드 최적화** (4개): 4코어 환경에 최적화
## 10. 빠른 적용 가이드
### Step 1: 애플리케이션 설정 (완료)
`application-prd.yml` 수정 완료
- Connection Pool: 35
- 배치 최적화 설정 추가
### Step 2: MariaDB 서버 설정 (실서버 작업)
#### 방법 2 권장: 별도 파일 생성
```bash
# 1. 성능 최적화 설정 파일 생성
sudo vi /etc/my.cnf.d/99-performance.cnf
# 2. 위 "추가할 설정 내용" 섹션의 전체 내용을 복사하여 붙여넣기
# 3. 저장 후 MariaDB 재시작
sudo systemctl restart mariadb
# 4. 재시작 확인
sudo systemctl status mariadb
# 5. 설정 적용 확인
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"
```
### Step 3: 애플리케이션 재시작
```bash
sudo systemctl restart vips
sudo systemctl status vips
```
### Step 4: 성능 테스트
- 740건 처리 시간 측정
- 목표: 14~15분 → **8~10분**
---
## 11. 추가 성능 개선 방법 (안정성 손상 없음)
### 1. 인덱스 최적화 (강력 권장)
#### TX_ID 인덱스 추가 (필수)
740건 처리 시 **3~5분 단축** 효과 (70% 개선)
```sql
-- TB_CAR_LEDGER_FRMBK.TX_ID 인덱스 추가
CREATE INDEX idx_car_ledger_frmbk_tx_id
ON tb_car_ledger_frmbk(TX_ID);
-- TB_CAR_BASS_MATTER_INQIRE.TX_ID 인덱스 추가
CREATE INDEX idx_car_bass_matter_inqire_tx_id
ON tb_car_bass_matter_inqire(TX_ID);
-- 인덱스 확인
SHOW INDEX FROM tb_car_ledger_frmbk;
SHOW INDEX FROM tb_car_bass_matter_inqire;
```
**예상 효과:**
- 현재: 8~10분 → **5~7분** (약 30% 추가 개선)
- UPDATE 성능: 100~1000배 향상
### 2. 기타 성능 개선
더 빠른 성능이 필요하다면:
1. **슬로우 쿼리 튜닝**: 슬로우 쿼리 로그 분석 후 개선
2. **SSD 스토리지**: HDD → SSD 교체 시 2~3배 성능 향상
3. **서버 스펙 업그레이드**: CPU/메모리 증설
---
**작성일**: 2025-12-15
**최종 수정일**: 2025-12-15 (4코어 16GB 환경, CentOS/RHEL 기준, 안정성 최우선)
**작성자**: Claude (AI Assistant)

@ -5,10 +5,11 @@
자동차 과태료 부과 대상(미필)을 검증하기 위한 비교 로직 정의서입니다.
### 구현 위치
- **Main Service**: `src/main/java/go/kr/project/carInspectionPenalty/registrationOm/service/impl/ComparisonOmServiceImpl.java`
- **Checker 클래스**: `src/main/java/go/kr/project/carInspectionPenalty/registrationOm/service/impl/om_checker/`
- `ProductUseOmChecker.java` - 1. 상품용 : 상품용
- `OwnerTransferOmChecker.java` - 2. 명의이전 소유자 확인 : 명의이전(25.9.5.) 이전소유자 상품용, 경상남도 창원시/ 현대캐피탈 주식회사, 미수검명의이전(25.5.19.)(37하1553)
- `TransferOmChecker.java` - 3. 이첩 : 경기도 과천시/ 이정호, 115일 도래지
- `TransferOmChecker.java` - 2. 이첩 : 경기도 과천시/ 이정호, 115일 도래지
- `OwnerTransferOmChecker.java` - 3. 명의이전 소유자 확인 : 명의이전(25.9.5.) 이전소유자 상품용, 경상남도 창원시/ 현대캐피탈 주식회사, 미수검명의이전(25.5.19.)(37하1553)
### 기본 설정
- 비교로직에 사용되는 API: `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출
@ -19,15 +20,58 @@
| 일자 | 변경 내용 | 비고 |
|------|----------|------|
| 2025-12-08 | om_checker 소스 기준 전면 작성 | 실제 코드와 일치하도록 정리 |
| 2025-12-15 | Step 0, Step 1 공통 로직 추가 및 순서 변경 | 이첩 순서 변경 (3번→2번) |
| 2025-12-09 | 검사유효기간 종료일 + 31일 로직 반영 | ProductUseOmChecker, OwnerTransferOmChecker |
| 2025-12-08 | om_checker 소스 기준 전면 작성 | 실제 코드와 일치하도록 정리 |
### 처리 규칙
> **중요**: 순서가 중요함!
> 1. 상품용 → 2. 명의이전 소유자 확인 → 3. 이첩
>
> **공통 로직 (한 번만 실행)**:
> - Step 0: 자동차기본정보 조회 (차량번호 + 오늘일자)
> - Step 1: 자동차기본정보 조회 (차대번호 + 부과일자)
>
> **Checker 순서**:
> 1. 상품용 → 2. 이첩 → 3. 명의이전 소유자 확인
> - 조건에 걸리는 순간 다음 차량번호 비교로 진행
> - 각 비교 로직별로 개별 API 호출 수행
> - 각 Checker는 공통 API 응답(step0Response, step1Response)을 파라미터로 받음
---
## 공통 로직 (ComparisonOmServiceImpl)
### Step 0: 자동차기본정보 조회 (현재 차량 정보)
**API 호출**: `getBasicInfo`
| 입력 | 값 |
|------|-----|
| 차량번호 | `vhclno` |
| 부과일자 | 오늘 날짜 (`LocalDate.now()`) |
**출력**:
- `step0Response` → 모든 Checker에 전달
- 주요 필드: `vin` (차대번호)
**실패 시**: 비교 로직 종료 (return null)
---
### Step 1: 자동차기본정보 조회 (부과일자 기준 정보)
**API 호출**: `getBasicInfo`
| 입력 | 값 |
|------|-----|
| 차대번호 | `step0Response.vin` |
| 부과일자 | 검사유효기간 종료일 + 146일 (`levyCrtrYmd`) |
**출력**:
- `step1Response` → 모든 Checker에 전달
- 주요 필드: `소유자명`, `대표소유자회원번호`
**실패 시**: 비교 로직 종료 (return null)
---
@ -38,21 +82,22 @@
**처리상태코드**: `02` (상품용)
**클래스**: `ProductUseOmChecker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사유효기간종료일+146일` | `차대번호(vin)`, `소유자명`, `대표소유자회원번호` | 부과일자 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
**참고**: Step 0, Step 1은 공통 로직에서 이미 호출됨
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") ← 상품용 아니면 return null
1. step1Response 소유자명.contains("상품용") ← 상품용 아니면 return null
2. Step2 대표소유자회원번호 == Step1 대표소유자회원번호
2. step1Response 대표소유자회원번호 == step0Response 대표소유자회원번호
← 불일치면 return null
3. 갑부에서 명의이전(11) 레코드 찾기
@ -61,7 +106,7 @@
- 가장 마지막 일자 선택
← 없으면 return null
4. Step4 대표소유자회원번호 == Step1 대표소유자회원번호
4. Step4 대표소유자회원번호 == step1Response 대표소유자회원번호
← 불일치면 return null
→ 모든 조건 충족: 상품용(02) 처리
@ -78,101 +123,103 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
---
### 2. 명의이전 소유자 확인 (`OwnerTransferOmChecker`)
### 2. 이첩 검증 (`TransferOmChecker`)
**처리상태코드**: `01` (그대로 접수)
**클래스**: `OwnerTransferOmChecker.java`
**처리상태코드**: `03` (이첩)
**클래스**: `TransferOmChecker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사유효기간종료일+146일` | `차대번호(vin)`, `소유자명` | 부과일자 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=검사유효기간종료일-90일` | `소유자명`, `소유자회원번호` | 90일 전 소유자 확인 |
| 3 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `소유자명`, `소유자회원번호` | 현재 소유자 확인 |
**참고**: Step 0, Step 1은 공통 로직에서 이미 호출됨
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
- CHG_YMD > 검사유효기간 종료일 + 31일
- 가장 마지막 일자 선택
← 없으면 return null
2. step1Response 소유자회원번호 != Step2 소유자회원번호
← 일치하면 return null
3. Step4 소유자명 확인
- "상품용" 포함 → 접수(01)
- "상품용" 미포함 → 접수(01)
3. step1Response 소유자회원번호 != Step3 소유자회원번호
← 일치하면 return null
→ 모든 조건 충족: 접수(01) 처리
4. 법정동코드 앞 4자리 == 사용자 조직코드 앞 4자리
← 일치하면 return null
→ 불일치: 이첩(03) 처리
```
#### 결과 분기
#### 결과 처리
| Step4 소유자명 | 처리상태코드 | 비고 형식 |
|---------------|-----------|-----------|
| 상품용 포함 | `01` (접수) | 명의이전(날짜) 이전소유자 상품용 |
| 상품용 미포함 | `01` (접수) | 명의이전(날짜) |
| 구분 | 비고 컬럼 형식 |
|------|---------------|
| 이첩 | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` |
#### DB 업데이트 필드
```java
existingData.setTaskPrcsSttsCd("01"); // 접수
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd("11");
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
existingData.setTaskPrcsSttsCd("03"); // 이첩
existingData.setCarBscMttrInqFlnm(step1RprsOwnrNm);
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
```
---
### 3. 이첩 검증 (`TransferOmChecker`)
### 3. 명의이전 소유자 확인 (`OwnerTransferOmChecker`)
**처리상태코드**: `03` (이첩)
**클래스**: `TransferOmChecker.java`
**처리상태코드**: `01` (그대로 접수)
**클래스**: `OwnerTransferOmChecker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사유효기간종료일+146일` | `소유자명`, `소유자회원번호`, `사용본거지법정동코드`, `차대번호` | 부과일자 기준 정보 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=검사유효기간종료일-90일` | `소유자명`, `소유자회원번호` | 90일 전 소유자 확인 |
| 3 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `소유자명`, `소유자회원번호` | 현재 소유자 확인 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
**참고**: Step 0, Step 1은 공통 로직에서 이미 호출됨
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. Step1 소유자회원번호 != Step2 소유자회원번호
← 불일치면 return null
3. Step1 소유자회원번호 != Step3 소유자회원번호
불일치면 return null
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
- CHG_YMD > 검사유효기간 종료일 + 31일
- 가장 마지막 일자 선택
없으면 return null
4. 법정동코드 앞 4자리 == 사용자 조직코드 앞 4자리
← 일치하면 return null
3. Step4 소유자명 확인
- "상품용" 포함 → 접수(01)
- "상품용" 미포함 → 접수(01)
불일치: 이첩(03) 처리
모든 조건 충족: 접수(01) 처리
```
#### 결과 처리
#### 결과 분기
| 구분 | 비고 컬럼 형식 |
|------|---------------|
| 이첩 | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` |
| Step4 소유자명 | 처리상태코드 | 비고 형식 |
|---------------|-----------|-----------|
| 상품용 포함 | `01` (접수) | 명의이전(날짜) 이전소유자 상품용 |
| 상품용 미포함 | `01` (접수) | 명의이전(날짜) |
#### DB 업데이트 필드
```java
existingData.setTaskPrcsSttsCd("03"); // 이첩
existingData.setCarBscMttrInqFlnm(step1RprsOwnrNm);
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
existingData.setTaskPrcsSttsCd("01"); // 접수
existingData.setCarBscMttrInqFlnm(step4OwnerName);
existingData.setCarRegFrmbkChgTaskSeCd("11");
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
```
---
@ -187,8 +234,21 @@ existingData.setCarBscMttrInqSggNm(sggNm);
┌──────────────────────────────────────────────┐
│ 공통: Step 0 - 자동차기본정보 (차량번호, 오늘) │
│ → step0Response (차대번호) │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ 공통: Step 1 - 자동차기본정보 (차대번호, 부과일자)│
│ → step1Response (소유자명) │
│ 부과일자 = 검사유효기간종료일 + 146일 │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ 1. 상품용 (ProductUseOmChecker) │
│ 조건: 부과일자 소유자명.contains("상품용") │
│ 조건: step1 소유자명.contains("상품용")
│ + 명의이전(11) 레코드 존재 │
│ + CHG_YMD <= 검사유효기간종료일+31일 │
│ + 소유자회원번호 일치 │
@ -197,21 +257,21 @@ existingData.setCarBscMttrInqSggNm(sggNm);
│ (조건 미충족)
┌──────────────────────────────────────────────┐
│ 2. 명의이전 소유자 확인 (OwnerTransferOmChecker)│
│ 조건: 부과일자 소유자명에 상품용 미포함 │
│ + CHG_YMD > 검사유효기간종료일+31일 │
│ + 명의이전(11) 레코드 존재 │
│ → 조건 충족: 접수(01) │
│ 2. 이첩 (TransferOmChecker) │
│ 조건: 소유자명에 상품용 미포함 │
│ + step1/step2 소유자 다름 │
│ + step1/step3 소유자 다름 │
│ + 법정동코드 앞4자리 != 조직코드 앞4자리│
│ → 조건 충족: 이첩(03) │
└──────────────────────────────────────────────┘
│ (조건 미충족)
┌──────────────────────────────────────────────┐
│ 3. 이첩 (TransferOmChecker) │
│ 조건: 소유자명에 상품용 미포함 │
│ + 1단계/2단계 소유자 같음 │
│ + 1단계/3단계 소유자 같음 │
│ + 법정동코드 앞4자리 != 조직코드 앞4자리│
│ → 조건 충족: 이첩(03) │
│ 3. 명의이전 소유자 확인 (OwnerTransferOmChecker)│
│ 조건: step1 소유자명에 상품용 미포함 │
│ + CHG_YMD > 검사유효기간종료일+31일 │
│ + 명의이전(11) 레코드 존재 │
│ → 조건 충족: 접수(01) │
└──────────────────────────────────────────────┘
│ (조건 미충족)
@ -222,22 +282,31 @@ existingData.setCarBscMttrInqSggNm(sggNm);
## 요약 정리
### 공통 로직
| Step | API 호출 | 입력 | 출력 | 용도 |
|------|---------|------|------|------|
| Step 0 | 자동차기본정보 | 차량번호 + 오늘일자 | step0Response (차대번호) | 현재 차량 기본 정보 |
| Step 1 | 자동차기본정보 | 차대번호 + 부과일자 | step1Response (소유자명, 회원번호) | 부과일자 기준 소유자 정보 |
### 처리상태코드 매핑
| 코드 | 상태명 | 처리 로직 | 클래스 |
|------|--------|--------------------------|--------|
| 02 | 상품용 | 부과일자 소유자가 상품용 + 명의이전(11) CHG_YMD <= 종료일+31일 | `ProductUseOmChecker` |
| 01 | 접수 | 검사유효기간종료일+31일 이후 명의이전 존재 | `OwnerTransferOmChecker` |
| 03 | 이첩 | 소유자 일치 + 법정동코드 불일치 | `TransferOmChecker` |
| 순서 | 코드 | 상태명 | 처리 로직 | 클래스 |
|------|------|--------|--------------------------|--------|
| 1 | 02 | 상품용 | step1 소유자가 상품용 + 명의이전(11) CHG_YMD <= 종료일+31일 | `ProductUseOmChecker` |
| 2 | 03 | 이첩 | step1/step2/step3 소유자 비교 + 법정동코드 불일치 | `TransferOmChecker` |
| 3 | 01 | 접수 | 검사유효기간종료일+31일 이후 명의이전 존재 | `OwnerTransferOmChecker` |
### 미필 vs 지연 비교
| 구분 | 미필 | 지연 |
|------|-----|------|
| 부과일자 | 검사유효기간 종료일 + 146일 | 검사일 |
| 상품용 조건 | 부과일자 기준 소유자명 | 검사일 기준 소유자명 |
| Step 1 기준일 | 부과일자 (종료일+146일) | 검사일 |
| 상품용 조건 | step1(부과일자) 기준 소유자명 | step1(검사일) 기준 소유자명 |
| Checker 개수 | 3개 | 7개 |
| 이첩 로직 | 3단계 API + 소유자 비교 | DAYCNT 기반 |
| 공통 로직 | Step 0, Step 1 | Step 0, Step 1 |
---
@ -249,6 +318,7 @@ existingData.setCarBscMttrInqSggNm(sggNm);
levyCrtrYmd = 검사유효기간 종료일 + 146일
// 처리상태코드
TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RECEIPT = "01"
TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE = "02"
TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER = "03"
@ -258,7 +328,7 @@ TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER = "03"
---
**문서 최종 수정일**: 2025-12-09
**실제 소스 코드 기준**: om_checker 폴더 내 Checker 클래스들
**분석 대상 클래스**: 3개 (ProductUseOmChecker, OwnerTransferOmChecker, TransferOmChecker)
**주요 변경사항**: 검사유효기간 종료일 + 31일 로직 적용
**문서 최종 수정일**: 2025-12-15
**실제 소스 코드 기준**: ComparisonOmServiceImpl, om_checker 폴더 내 Checker 클래스들
**분석 대상 클래스**: 4개 (ComparisonOmServiceImpl + 3개 Checker)
**주요 변경사항**: Step 0, Step 1 공통 로직 추가, 이첩 순서 변경 (3번→2번)

@ -5,14 +5,15 @@
자동차 과태료 부과 대상(지연)을 검증하기 위한 비교 로직 정의서입니다.
### 구현 위치
- **Main Service**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/ComparisonServiceImpl.java`
- **Checker 클래스**: `src/main/java/go/kr/project/carInspectionPenalty/registration/service/impl/delay_checker/`
- `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 = 인천광역시 부평구/ (주)우리카드, 검사일사용본거지
- `TransferCase115DayChecker.java` - 4. 이첩 : case 1 = 경상남도 창원시/ 현대캐피탈 주식회사, 115일 도래지 case 2 = 인천광역시 부평구/ (주)우리카드, 검사일사용본거지
- `ProductLevyOver31Checker.java` - 5. 날짜수정후부과(명의이전 이전소유자 상품용, 31일 초과) : 경기도 고양시/ 장준혁, 미수검명의이전(25.8.19.)
- `OwnerLevyOver31Checker.java` - 6. 날짜수정후부과(순수 명의이전, 31일 초과) : 대구광역시 달서구/ 하나캐피탈(주), 미수검명의이전(25.9.3.)
### 기본 설정
- 비교로직에 사용되는 API: `ExternalVehicleApiServiceImpl.getBasicInfo`, `getLedgerInfo` 호출
@ -23,6 +24,7 @@
| 일자 | 변경 내용 | 비고 |
|------|----------|------|
| 2025-12-15 | Step 0, Step 1 공통 로직 추가 및 순서 변경 | 이첩 순서 변경 (6번→4번) |
| 2025-12-08 | delay_checker 소스 기준 전면 재작성 | 실제 코드와 일치하도록 정리 |
| 2025-12-03 | 실제 소스 코드 기준으로 전면 재작성 | Javadoc과 실제 로직 차이 명시 |
| 2025-12-02 | 내사종결 로직 추가 및 상품용 조건 변경 | 명의이전 31일 이내 조건 추가 |
@ -30,9 +32,51 @@
### 처리 규칙
> **중요**: 순서가 중요함!
> 1. 상품용(명의이전) → 1-1. 상품용(변경등록) → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 날짜수정(상품용) → 5. 날짜수정(명의이전) → 6. 이첩
>
> **공통 로직 (한 번만 실행)**:
> - Step 0: 자동차기본정보 조회 (차량번호 + 오늘일자)
> - Step 1: 자동차기본정보 조회 (차대번호 + 검사일)
>
> **Checker 순서**:
> 1. 상품용(명의이전) → 1-1. 상품용(변경등록) → 2. 내사종결(상품용) → 3. 내사종결(명의이전) → 4. 이첩 → 5. 날짜수정(상품용) → 6. 날짜수정(명의이전)
> - 조건에 걸리는 순간 다음 차량번호 비교로 진행
> - 각 비교 로직별로 개별 API 호출 수행
> - 각 Checker는 공통 API 응답(step0Response, step1Response)을 파라미터로 받음
---
## 공통 로직 (ComparisonServiceImpl)
### Step 0: 자동차기본정보 조회 (현재 차량 정보)
**API 호출**: `getBasicInfo`
| 입력 | 값 |
|------|-----|
| 차량번호 | `vhclno` (특수문자 '*' 제거) |
| 부과일자 | 오늘 날짜 (`LocalDate.now()`) |
**출력**:
- `step0Response` → 모든 Checker에 전달
- 주요 필드: `vin` (차대번호)
**실패 시**: 비교 로직 종료 (return null)
---
### Step 1: 자동차기본정보 조회 (검사일 기준 정보)
**API 호출**: `getBasicInfo`
| 입력 | 값 |
|------|-----|
| 차대번호 | `step0Response.vin` |
| 부과일자 | 검사일 (`inspYmd`) |
**출력**:
- `step1Response` → 모든 Checker에 전달
- 주요 필드: `소유자명`, `대표소유자회원번호`
**실패 시**: 비교 로직 종료 (return null)
---
@ -43,19 +87,20 @@
**처리상태코드**: `02` (상품용)
**클래스**: `ProductUseChecker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호(vin)`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
**참고**: Step 0, Step 1은 공통 로직에서 이미 호출됨
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") ← 상품용 아니면 return null
1. step1Response 소유자명.contains("상품용") ← 상품용 아니면 return null
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
@ -63,7 +108,7 @@
- 가장 마지막 일자 선택
← 없으면 return null
3. Step4 대표소유자회원번호 == Step1 대표소유자회원번호
3. Step4 대표소유자회원번호 == step1Response 대표소유자회원번호
← 불일치면 return null
→ 모든 조건 충족: 상품용(02) 처리
@ -85,19 +130,18 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
**처리상태코드**: `02` (상품용)
**클래스**: `ProductUseChnageChecker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호(vin)`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 변경등록 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 변경등록 시점 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 변경등록 시점 소유자 확인 |
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") ← 상품용 아니면 return null
1. step1Response 소유자명.contains("상품용") ← 상품용 아니면 return null
2. 갑부에서 변경등록(21) 레코드 찾기
- CHG_TASK_SE_CD == "21"
@ -106,7 +150,7 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
- 가장 마지막 일자 선택
← 없으면 return null
3. Step4 대표소유자회원번호 == Step1 대표소유자회원번호
3. Step4 대표소유자회원번호 == step1Response 대표소유자회원번호
← 불일치면 return null
→ 모든 조건 충족: 상품용(02) 처리
@ -128,20 +172,19 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
**처리상태코드**: `04` (내사종결)
**클래스**: `ProductCloseWithin31Checker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
@ -151,7 +194,7 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
3. 일수 계산: 명의이전일자 ~ 검사일
- 0~31일 이내 → 계속 진행
- 31일 초과 → return null (4번에서 처리)
- 31일 초과 → return null (5번에서 처리)
4. Step4 소유자명.contains("상품용")
← 상품용 아니면 return null
@ -175,20 +218,19 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
**처리상태코드**: `04` (내사종결)
**클래스**: `OwnerCloseWithin31Checker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
@ -198,9 +240,9 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
3. 일수 계산: 명의이전일자 ~ 검사일
- 0~31일 이내 → 계속 진행
- 31일 초과 → return null (5번에서 처리)
- 31일 초과 → return null (6번에서 처리)
4. Step4 대표소유자회원번호 == Step1 대표소유자회원번호
4. Step4 대표소유자회원번호 == step1Response 대표소유자회원번호
← 불일치면 return null
→ 모든 조건 충족: 내사종결(04) 처리
@ -217,25 +259,72 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
---
### 4. 날짜수정후부과 - 명의이전 이전소유자 상품용, 31일 초과 (`ProductLevyOver31Checker`)
### 4. 이첩 검증 (`TransferCase115DayChecker`)
**처리상태코드**: `03` (이첩)
**클래스**: `TransferCase115DayChecker.java`
#### 추가 API 호출
| API | 입력 파라미터 | 출력 데이터 | 용도 |
|-----|--------------|-------------|------|
| 자동차기본정보 | `차량번호`, `부과일자=부과기준일` | `사용본거지법정동코드` | 부과기준일 기준 사용본거지 확인 |
**참고**: Step 0, Step 1은 공통 로직에서 이미 호출됨
#### 비교 조건
```
1. DAYCNT 값으로 부과기준일 계산
- DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일)
- DAYCNT <= 115: 이첩-1 (부과기준일 = 검사일자)
← DAYCNT 없으면 return null
2. 자동차기본정보(차량번호, 부과기준일) 호출
→ 사용본거지법정동코드 조회
3. 법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
← 일치하면 return null
→ 불일치: 이첩(03) 처리
```
#### 결과 처리
| 구분 | 조건 | 비고 컬럼 형식 |
|------|------|---------------|
| 이첩-1 | `DAYCNT <= 115` | `"{시군구명}, 검사일사용본거지, [사용자 조직코드 앞 4자리: {userOrg4}, 법정동명: {sggNm}]"` |
| 이첩-2 | `DAYCNT > 115` | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` |
#### DB 업데이트 필드
```java
existingData.setTaskPrcsSttsCd("03"); // 이첩
existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm()); // 기존 소유자명 유지
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
```
---
### 5. 날짜수정후부과 - 명의이전 이전소유자 상품용, 31일 초과 (`ProductLevyOver31Checker`)
**처리상태코드**: `05` (날짜 수정 후 부과)
**클래스**: `ProductLevyOver31Checker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD-1일` | `소유자명` | 명의이전 직전 소유자 확인 |
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
@ -264,25 +353,24 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
---
### 5. 날짜수정후부과 - 순수 명의이전, 31일 초과 (`OwnerLevyOver31Checker`)
### 6. 날짜수정후부과 - 순수 명의이전, 31일 초과 (`OwnerLevyOver31Checker`)
**처리상태코드**: `05` (날짜 수정 후 부과)
**클래스**: `OwnerLevyOver31Checker.java`
#### API 호출 순서
#### 추가 API 호출 순서
| 순서 | API | 입력 파라미터 | 출력 데이터 | 용도 |
|------|-----|--------------|-------------|------|
| 1 | 자동차기본정보 | `차량번호`, `부과일자=검사일` | `차대번호`, `소유자명`, `대표소유자회원번호` | 검사일 기준 소유자 확인 |
| 2 | 자동차기본정보 | `1.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 2 | 자동차기본정보 | `step0.차대번호`, `부과일자=오늘` | `차량번호`, `성명`, `주민번호`, `법정동코드` | 현재 소유자 정보 |
| 3 | 자동차등록원부(갑) | `2.차량번호`, `2.성명`, `2.주민번호`, `2.법정동코드` | 갑부 상세 List | 명의이전 이력 조회 |
| 4 | 자동차기본정보 | `1.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
| 4 | 자동차기본정보 | `step0.차대번호`, `부과일자=CHG_YMD` | `소유자명`, `대표소유자회원번호` | 명의이전 시점 소유자 확인 |
#### 비교 조건
```
1. Step1 소유자명.contains("상품용") → return null (1번에서 처리됨)
!Step1 소유자명.contains("상품용") → 계속 진행
1. step1Response 소유자명.contains("상품용") → return null (1번에서 처리됨)
!step1Response 소유자명.contains("상품용") → 계속 진행
2. 갑부에서 명의이전(11) 레코드 찾기
- CHG_TASK_SE_CD == "11"
@ -294,7 +382,7 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
- 31일 초과 → 계속 진행
- 0~31일 이내 → return null (3번에서 처리)
4. Step4 대표소유자회원번호 == Step1 대표소유자회원번호
4. Step4 대표소유자회원번호 == step1Response 대표소유자회원번호
← 불일치면 return null
→ 모든 조건 충족: 날짜수정후부과(05) 처리
@ -311,52 +399,6 @@ existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd());
---
### 6. 이첩 검증 (`TransferCase115DayChecker`)
**처리상태코드**: `03` (이첩)
**클래스**: `TransferCase115DayChecker.java`
#### API 호출
| API | 입력 파라미터 | 출력 데이터 | 용도 |
|-----|--------------|-------------|------|
| 자동차기본정보 | `차량번호`, `부과일자=부과기준일` | `사용본거지법정동코드` | 부과기준일 기준 사용본거지 확인 |
#### 비교 조건
```
1. DAYCNT 값으로 부과기준일 계산
- DAYCNT > 115: 이첩-2 (부과기준일 = 검사종료일자 + 115일)
- DAYCNT <= 115: 이첩-1 (부과기준일 = 검사일자)
← DAYCNT 없으면 return null
2. 자동차기본정보(차량번호, 부과기준일) 호출
→ 사용본거지법정동코드 조회
3. 법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
← 일치하면 return null
→ 불일치: 이첩(03) 처리
```
#### 결과 처리
| 구분 | 조건 | 비고 컬럼 형식 |
|------|------|---------------|
| 이첩-1 | `DAYCNT <= 115` | `"{시군구명}, 검사일사용본거지, [사용자 조직코드 앞 4자리: {userOrg4}, 법정동명: {sggNm}]"` |
| 이첩-2 | `DAYCNT > 115` | `"{시군구명}, 115일 도래지, [법정동코드: {legalDong4}, 법정동명: {sggNm}]"` |
#### DB 업데이트 필드
```java
existingData.setTaskPrcsSttsCd("03"); // 이첩
existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm()); // 기존 소유자명 유지
existingData.setCarBscMttrInqSggCd(sggCd);
existingData.setCarBscMttrInqSggNm(sggNm);
```
---
## 처리 흐름도
```
@ -367,8 +409,20 @@ existingData.setCarBscMttrInqSggNm(sggNm);
┌──────────────────────────────────────────────┐
│ 공통: Step 0 - 자동차기본정보 (차량번호, 오늘) │
│ → step0Response (차대번호) │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ 공통: Step 1 - 자동차기본정보 (차대번호, 검사일)│
│ → step1Response (소유자명) │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ 1. 상품용-명의이전 (ProductUseChecker) │
│ 조건: 검사일 소유자명.contains("상품용") │
│ 조건: step1 소유자명.contains("상품용")
│ + 명의이전(11) 레코드 존재 │
│ → 조건 충족: 상품용(02) │
└──────────────────────────────────────────────┘
@ -376,7 +430,7 @@ existingData.setCarBscMttrInqSggNm(sggNm);
┌──────────────────────────────────────────────┐
│ 1-1. 상품용-변경등록 (ProductUseChnageChecker)│
│ 조건: 검사일 소유자명.contains("상품용")
│ 조건: step1 소유자명.contains("상품용")
│ + 변경등록(21) + 성명변경 포함 │
│ → 조건 충족: 상품용(02) │
└──────────────────────────────────────────────┘
@ -397,23 +451,23 @@ existingData.setCarBscMttrInqSggNm(sggNm);
│ (조건 미충족)
┌──────────────────────────────────────────────┐
│ 4. 날짜수정-상품용 (ProductLevyOver31Checker)
│ 조건: 31일 초과 + 명의이전 전 상품용
│ → 조건 충족: 날짜수정후부과(05)
│ 4. 이첩 (TransferCase115DayChecker)
│ 조건: 법정동코드 앞4자리 != 조직코드 앞4자리
│ → 조건 충족: 이첩(03)
└──────────────────────────────────────────────┘
│ (조건 미충족)
┌──────────────────────────────────────────────┐
│ 5. 날짜수정-명의이전 (OwnerLevyOver31Checker) │
│ 조건: 31일 초과 + 동일 소유자
│ 5. 날짜수정-상품용 (ProductLevyOver31Checker) │
│ 조건: 31일 초과 + 명의이전 전 상품용
│ → 조건 충족: 날짜수정후부과(05) │
└──────────────────────────────────────────────┘
│ (조건 미충족)
┌──────────────────────────────────────────────┐
│ 6. 이첩 (TransferCase115DayChecker)
│ 조건: 법정동코드 앞4자리 != 조직코드 앞4자리
│ → 조건 충족: 이첩(03)
│ 6. 날짜수정-명의이전 (OwnerLevyOver31Checker)
│ 조건: 31일 초과 + 동일 소유자
│ → 조건 충족: 날짜수정후부과(05)
└──────────────────────────────────────────────┘
│ (조건 미충족)
@ -424,25 +478,32 @@ existingData.setCarBscMttrInqSggNm(sggNm);
## 요약 정리
### 공통 로직
| Step | API 호출 | 입력 | 출력 | 용도 |
|------|---------|------|------|------|
| Step 0 | 자동차기본정보 | 차량번호 + 오늘일자 | step0Response (차대번호) | 현재 차량 기본 정보 |
| Step 1 | 자동차기본정보 | 차대번호 + 검사일 | step1Response (소유자명, 회원번호) | 검사일 기준 소유자 정보 |
### 처리상태코드 매핑
| 코드 | 상태명 | 처리 로직 | 클래스 |
|------|--------|----------|--------|
| 02 | 상품용 | 검사일 소유자가 상품용 + 명의이전(11) | `ProductUseChecker` |
| 02 | 상품용 | 검사일 소유자가 상품용 + 변경등록(21) + 성명변경 | `ProductUseChnageChecker` |
| 04 | 내사종결 | 명의이전 전 상품용 (31일 이내) | `ProductCloseWithin31Checker` |
| 04 | 내사종결 | 순수 명의이전 (31일 이내) | `OwnerCloseWithin31Checker` |
| 05 | 날짜수정후부과 | 명의이전 전 상품용 (31일 초과) | `ProductLevyOver31Checker` |
| 05 | 날짜수정후부과 | 순수 명의이전 (31일 초과) | `OwnerLevyOver31Checker` |
| 03 | 이첩 | 법정동코드 불일치 | `TransferCase115DayChecker` |
| 순서 | 코드 | 상태명 | 처리 로직 | 클래스 |
|------|------|--------|----------|--------|
| 1 | 02 | 상품용 | step1 소유자가 상품용 + 명의이전(11) | `ProductUseChecker` |
| 1-1 | 02 | 상품용 | step1 소유자가 상품용 + 변경등록(21) + 성명변경 | `ProductUseChnageChecker` |
| 2 | 04 | 내사종결 | 명의이전 전 상품용 (31일 이내) | `ProductCloseWithin31Checker` |
| 3 | 04 | 내사종결 | 순수 명의이전 (31일 이내) | `OwnerCloseWithin31Checker` |
| 4 | 03 | 이첩 | 법정동코드 불일치 | `TransferCase115DayChecker` |
| 5 | 05 | 날짜수정후부과 | 명의이전 전 상품용 (31일 초과) | `ProductLevyOver31Checker` |
| 6 | 05 | 날짜수정후부과 | 순수 명의이전 (31일 초과) | `OwnerLevyOver31Checker` |
### 상품용 vs 순수 명의이전 비교
| 구분 | 상품용 케이스 (2, 4번) | 순수 명의이전 케이스 (3, 5번) |
| 구분 | 상품용 케이스 (2, 5번) | 순수 명의이전 케이스 (3, 6번) |
|------|----------------------|----------------------------|
| 검사일 소유자 | 상품용 아님 | 상품용 아님 |
| 명의이전 직전 소유자 | 상품용 **포함** | 소유자 회원번호 **일치** |
| Step 4 확인사항 | 소유자명.contains("상품용") | 대표소유자회원번호 == Step1 |
| Step 4 확인사항 | 소유자명.contains("상품용") | 대표소유자회원번호 == step1 |
| 갑부 검색 조건 | CHG_YMD <= 검사일 | 유효기간만료일-90일 <= CHG_YMD <= 검사종료일자 |
| 일수 기준 | 31일 이내/초과 | 31일 이내/초과 |
| 처리상태코드 | 31일 이내: 04, 초과: 05 | 31일 이내: 04, 초과: 05 |
@ -469,6 +530,7 @@ TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY = "05"
---
**문서 작성 완료일**: 2025-12-08
**실제 소스 코드 기준**: delay_checker 폴더 내 Checker 클래스들
**분석 대상 클래스**: 7개 (ProductUseChecker, ProductUseChnageChecker, ProductCloseWithin31Checker, OwnerCloseWithin31Checker, ProductLevyOver31Checker, OwnerLevyOver31Checker, TransferCase115DayChecker)
**문서 최종 수정일**: 2025-12-15
**실제 소스 코드 기준**: ComparisonServiceImpl, delay_checker 폴더 내 Checker 클래스들
**분석 대상 클래스**: 8개 (ComparisonServiceImpl + 7개 Checker)
**주요 변경사항**: Step 0, Step 1 공통 로직 추가, 이첩 순서 변경 (6번→4번)

@ -60,7 +60,7 @@ public class CarFfnlgTrgtExcelVO {
/** 일수 */
@ExcelColumn(headerName = "일수", headerWidth = 10, align = ExcelColumn.Align.RIGHT)
private Long daycnt;
private String daycnt;
/** 재검여부 */
@ExcelColumn(headerName = "재검여부", headerWidth = 12, align = ExcelColumn.Align.CENTER)

@ -996,8 +996,9 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
}
final go.kr.project.login.model.LoginUserVO finalUserInfo = userInfo;
// I/O 작업이므로 CPU 코어 수의 2배로 스레드 풀 생성
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
// I/O 작업이므로 CPU 코어 수의 4배로 스레드 풀 생성 (4코어 16GB 환경 최적화)
// 4코어 × 4 = 16개 스레드 (메모리 안정성 고려)
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 4;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
log.info("병렬처리 스레드 풀 크기: {}", threadPoolSize);

@ -446,8 +446,9 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
}
final go.kr.project.login.model.LoginUserVO finalUserInfo = userInfo;
// I/O 작업이므로 CPU 코어 수의 2배로 스레드 풀 생성
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 2;
// I/O 작업이므로 CPU 코어 수의 4배로 스레드 풀 생성 (4코어 16GB 환경 최적화)
// 4코어 × 4 = 16개 스레드 (메모리 안정성 고려)
int threadPoolSize = Runtime.getRuntime().availableProcessors() * 4;
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
log.info("병렬처리 스레드 풀 크기: {}", threadPoolSize);

@ -15,16 +15,16 @@ spring:
hikari:
# ==========================================
# 커넥션 풀 크기 설정 (4코어 32GB 서버 기준)
# 커넥션 풀 크기 설정 (4코어 16GB 서버 기준)
# ==========================================
# 동시에 사용할 수 있는 최대 커넥션 수
# 권장값: (코어수 × 2) + (동시사용자 × 0.1) = (4 × 2) + (300 × 0.1) = 38 → 40
# 이 값을 초과하면 새로운 커넥션 요청이 대기 상태가 됨
maximum-pool-size: 40
# 권장값: 병렬 처리 스레드 풀(코어 × 4) 보다 여유 있게 설정
# 스레드 풀 크기(16) + 여유(19) = 35
maximum-pool-size: 35
# 풀에서 유지할 최소 유휴 커넥션 수
# 권장값: maximum-pool-size의 25% (40 × 0.25 = 10)
# 권장값: maximum-pool-size의 약 40% (35 × 0.4 ≈ 15)
# 갑작스러운 부하 증가 시 빠른 응답을 위해 미리 준비된 커넥션 유지
minimum-idle: 10
minimum-idle: 15
# ==========================================
# 커넥션 타임아웃 설정
@ -59,6 +59,30 @@ spring:
#connection-test-query: select 1
auto-commit: false
# ==========================================
# MariaDB 배치 최적화 설정 (성능 향상)
# ==========================================
data-source-properties:
# MariaDB 배치 쿼리 재작성 활성화 (가장 중요!)
# INSERT/UPDATE를 묶어서 한 번에 전송 → DB 왕복 횟수 감소
rewriteBatchedStatements: true
# 서버 측 PreparedStatement 사용
# 쿼리 파싱/컴파일 결과를 DB 서버에 캐싱 → 반복 쿼리 성능 향상
useServerPrepStmts: true
# PreparedStatement 캐싱 활성화
# 클라이언트 측에서 PreparedStatement 재사용 → 메모리 절약
cachePrepStmts: true
# PreparedStatement 캐시 크기
# 최대 250개의 PreparedStatement를 캐싱
prepStmtCacheSize: 250
# 캐싱할 SQL 최대 길이
# 2048자 이하의 SQL만 캐싱 (대부분의 쿼리 포함)
prepStmtCacheSqlLimit: 2048
# Server configuration
server:
port: 18080

@ -150,7 +150,7 @@
<!-- 과태료 대상 미필 목록 엑셀 다운로드용 조회 -->
<select id="selectListForExcel" parameterType="CarFfnlgTrgtIncmpVO" resultType="CarFfnlgTrgtIncmpExcelVO">
SELECT
DATE_FORMAT(STR_TO_DATE(t.RCPT_YMD, '%Y%m%d'), '%Y-%m-%d') AS rcptYmd,
t.RCPT_YMD AS rcptYmd,
t.PRGRM_ID AS prgrmId,
t.PRCS_YMD AS prcsYmd,
t.NO AS no,
@ -162,13 +162,13 @@
t.USE_STRHLD_ADDR_DTL AS useStrhldAddrDtl,
t.INSP_VLD_PRD AS inspVldPrd,
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

Loading…
Cancel
Save