parent
94cbef2857
commit
1f442a9472
@ -0,0 +1,36 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison;
|
||||
|
||||
import go.kr.project.api.model.VehicleApiResponseVO;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 비교 로직 실행에 필요한 컨텍스트 정보
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public class ComparisonContext {
|
||||
|
||||
/**
|
||||
* 기존 과태료 대상 데이터
|
||||
*/
|
||||
private final CarFfnlgTrgtVO existingData;
|
||||
|
||||
/**
|
||||
* API 응답 데이터
|
||||
*/
|
||||
private final VehicleApiResponseVO apiResponse;
|
||||
|
||||
/**
|
||||
* 등록자 (사용자 ID)
|
||||
*/
|
||||
private final String userId;
|
||||
|
||||
/**
|
||||
* 차량번호 (편의성을 위해 추가)
|
||||
*/
|
||||
public String getVhclno() {
|
||||
return existingData != null ? existingData.getVhclno() : null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 비교 로직 실행 결과
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public class ComparisonResult {
|
||||
|
||||
/**
|
||||
* 비교 로직이 적용되었는지 여부
|
||||
* true: 이 규칙이 적용되어 처리됨 (다음 규칙 실행 중단)
|
||||
* false: 이 규칙에 해당하지 않음 (다음 규칙 계속 실행)
|
||||
*/
|
||||
private final boolean applied;
|
||||
|
||||
/**
|
||||
* 처리 상태 코드
|
||||
* 02=상품용, 03=이첩, 04=내사종결 등
|
||||
*/
|
||||
private final String statusCode;
|
||||
|
||||
/**
|
||||
* 처리 결과 메시지
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* 비교 로직에 해당하지 않는 경우의 결과 생성
|
||||
*/
|
||||
public static ComparisonResult notApplied() {
|
||||
return ComparisonResult.builder()
|
||||
.applied(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 비교 로직이 적용된 경우의 결과 생성
|
||||
*/
|
||||
public static ComparisonResult applied(String statusCode, String message) {
|
||||
return ComparisonResult.builder()
|
||||
.applied(true)
|
||||
.statusCode(statusCode)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison;
|
||||
|
||||
/**
|
||||
* 과태료 대상 비교 규칙 인터페이스
|
||||
*
|
||||
* <p>Chain of Responsibility 패턴을 사용하여 여러 비교 규칙을 순차적으로 실행합니다.</p>
|
||||
*
|
||||
* <p>각 규칙은 다음 순서로 동작합니다:</p>
|
||||
* <ol>
|
||||
* <li>비교 로직 실행</li>
|
||||
* <li>해당 규칙에 적용되면 DB 업데이트 후 ComparisonResult.applied() 반환</li>
|
||||
* <li>해당 규칙에 적용되지 않으면 ComparisonResult.notApplied() 반환</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>규칙이 applied=true를 반환하면 체인 실행이 중단되고, false를 반환하면 다음 규칙이 실행됩니다.</p>
|
||||
*/
|
||||
public interface ComparisonRule {
|
||||
|
||||
/**
|
||||
* 비교 규칙을 실행합니다.
|
||||
*
|
||||
* @param context 비교에 필요한 컨텍스트 정보
|
||||
* @return 비교 결과 (applied=true면 체인 중단, false면 다음 규칙 실행)
|
||||
*/
|
||||
ComparisonResult execute(ComparisonContext context);
|
||||
|
||||
/**
|
||||
* 규칙의 이름을 반환합니다. (로깅 및 디버깅용)
|
||||
*
|
||||
* @return 규칙 이름
|
||||
*/
|
||||
String getRuleName();
|
||||
|
||||
/**
|
||||
* 규칙의 실행 순서를 반환합니다. (낮을수록 먼저 실행)
|
||||
*
|
||||
* @return 실행 순서 (기본값: 100)
|
||||
*/
|
||||
default int getOrder() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 비교 규칙 프로세서
|
||||
*
|
||||
* <p>여러 비교 규칙을 순차적으로 실행하고 결과를 반환합니다.</p>
|
||||
*
|
||||
* <p>실행 방식:</p>
|
||||
* <ol>
|
||||
* <li>모든 규칙을 getOrder() 순서대로 정렬</li>
|
||||
* <li>순서대로 각 규칙 실행</li>
|
||||
* <li>규칙이 applied=true를 반환하면 즉시 중단</li>
|
||||
* <li>모든 규칙이 applied=false를 반환하면 null 반환</li>
|
||||
* </ol>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ComparisonRuleProcessor {
|
||||
|
||||
private final List<ComparisonRule> rules;
|
||||
|
||||
/**
|
||||
* 생성자 주입으로 모든 ComparisonRule 빈을 자동으로 주입받습니다.
|
||||
* Spring이 자동으로 모든 ComparisonRule 구현체를 찾아서 리스트로 주입합니다.
|
||||
*/
|
||||
public ComparisonRuleProcessor(List<ComparisonRule> rules) {
|
||||
// getOrder() 순서대로 정렬
|
||||
this.rules = rules.stream()
|
||||
.sorted(Comparator.comparingInt(ComparisonRule::getOrder))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("========== 비교 규칙 프로세서 초기화 ==========");
|
||||
log.info("등록된 규칙 개수: {}", this.rules.size());
|
||||
this.rules.forEach(rule ->
|
||||
log.info(" - [순서: {}] {}", rule.getOrder(), rule.getRuleName())
|
||||
);
|
||||
log.info("============================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 비교 규칙을 순차적으로 실행합니다.
|
||||
*
|
||||
* @param context 비교 컨텍스트
|
||||
* @return 처리 결과 (적용된 규칙이 없으면 null)
|
||||
*/
|
||||
public ComparisonResult process(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
log.debug("========== 비교 규칙 체인 시작: {} ==========", vhclno);
|
||||
|
||||
for (ComparisonRule rule : rules) {
|
||||
log.debug("[{}] 규칙 실행 중... 차량번호: {}", rule.getRuleName(), vhclno);
|
||||
|
||||
try {
|
||||
ComparisonResult result = rule.execute(context);
|
||||
|
||||
if (result.isApplied()) {
|
||||
log.info("[{}] 규칙 적용됨! 차량번호: {}, 상태코드: {}, 메시지: {}",
|
||||
rule.getRuleName(), vhclno, result.getStatusCode(), result.getMessage());
|
||||
log.debug("========== 비교 규칙 체인 종료 (규칙 적용): {} ==========", vhclno);
|
||||
return result;
|
||||
}
|
||||
|
||||
log.debug("[{}] 규칙 적용되지 않음. 다음 규칙으로 이동...", rule.getRuleName());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[{}] 규칙 실행 중 오류 발생! 차량번호: {}", rule.getRuleName(), vhclno, e);
|
||||
throw new RuntimeException(
|
||||
String.format("[%s] 규칙 실행 중 오류 발생: %s - %s",
|
||||
rule.getRuleName(), vhclno, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("========== 비교 규칙 체인 종료 (적용된 규칙 없음): {} ==========", vhclno);
|
||||
return null; // 적용된 규칙이 없음
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 규칙 목록을 반환합니다. (디버깅용)
|
||||
*/
|
||||
public List<ComparisonRule> getRules() {
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,261 @@
|
||||
# 과태료 대상 비교 규칙 모듈
|
||||
|
||||
## 개요
|
||||
|
||||
이 모듈은 과태료 대상 차량을 API 응답 데이터와 비교하여 자동으로 분류하는 기능을 제공합니다.
|
||||
**Chain of Responsibility 패턴**을 사용하여 각 비교 규칙을 독립적으로 관리하고 확장할 수 있습니다.
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```
|
||||
comparison/
|
||||
├── ComparisonRule.java # 비교 규칙 인터페이스
|
||||
├── ComparisonContext.java # 비교에 필요한 데이터 컨테이너
|
||||
├── ComparisonResult.java # 비교 결과 객체
|
||||
├── ComparisonRuleProcessor.java # 규칙 실행 체인 관리자
|
||||
└── rules/ # 개별 규칙 구현체
|
||||
├── ProductUseComparisonRule.java # 상품용 규칙
|
||||
└── TransferComparisonRule.java # 이첩 규칙
|
||||
```
|
||||
|
||||
## 동작 방식
|
||||
|
||||
1. **ComparisonRuleProcessor**가 모든 `@Component` 규칙을 자동으로 찾아서 등록
|
||||
2. 규칙들을 `getOrder()` 순서대로 정렬 (낮을수록 먼저 실행)
|
||||
3. 각 규칙을 순차적으로 실행
|
||||
4. 규칙이 `applied=true`를 반환하면 즉시 중단
|
||||
5. 모든 규칙이 `applied=false`를 반환하면 "정상" 처리
|
||||
|
||||
## 새로운 비교 규칙 추가 방법
|
||||
|
||||
### 1. 규칙 클래스 생성
|
||||
|
||||
`rules` 패키지에 새로운 규칙 클래스를 생성합니다.
|
||||
|
||||
```java
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.*;
|
||||
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 내사종결 비교 규칙
|
||||
*
|
||||
* 적용 조건:
|
||||
* - 예시: 차량이 말소된 경우
|
||||
*
|
||||
* 처리 내용:
|
||||
* - TASK_PRCS_STTS_CD = 04 (내사종결)
|
||||
* - TASK_PRCS_YMD = 현재 날짜
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvestigationClosedComparisonRule implements ComparisonRule {
|
||||
|
||||
private static final String STATUS_CODE = "04"; // 내사종결
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper;
|
||||
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
|
||||
// 1. API 응답 데이터 유효성 검사
|
||||
if (context.getApiResponse().getBasicInfo() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
|
||||
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}",
|
||||
getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
// 2. 필요한 데이터 추출
|
||||
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
|
||||
|
||||
// 3. 비교 로직 (말소된 차량인지 확인)
|
||||
if (ersrRegistSeCode == null || ersrRegistSeCode.isEmpty()) {
|
||||
log.debug("[{}] 말소되지 않은 차량입니다. 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
log.info("[{}] 내사종결 감지! 차량번호: {}, 말소구분: {}",
|
||||
getRuleName(), vhclno, ersrRegistSeCode);
|
||||
|
||||
// 4. DB 업데이트
|
||||
CarFfnlgTrgtVO updateData = context.getExistingData();
|
||||
updateData.setTaskPrcsSttsCd(STATUS_CODE);
|
||||
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
// 필요시 추가 필드 설정
|
||||
|
||||
int updateResult = mapper.update(updateData);
|
||||
|
||||
// 5. 결과 반환
|
||||
if (updateResult > 0) {
|
||||
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.applied(STATUS_CODE, "내사종결로 처리되었습니다.");
|
||||
} else {
|
||||
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
|
||||
throw new RuntimeException(String.format("내사종결 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() {
|
||||
return "내사종결";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 30; // 상품용(10), 이첩(20) 다음으로 실행
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 자동 등록
|
||||
|
||||
Spring이 자동으로 `@Component` 어노테이션이 붙은 클래스를 찾아서 등록합니다.
|
||||
**별도의 설정 파일 수정이 필요 없습니다!**
|
||||
|
||||
### 3. 실행 순서 제어
|
||||
|
||||
`getOrder()` 메서드로 실행 순서를 제어할 수 있습니다.
|
||||
|
||||
```java
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 30; // 숫자가 낮을수록 먼저 실행
|
||||
}
|
||||
```
|
||||
|
||||
**현재 순서:**
|
||||
- 상품용: 10
|
||||
- 이첩: 20
|
||||
- (향후 추가): 30, 40, 50...
|
||||
|
||||
## 기존 규칙 예제
|
||||
|
||||
### 상품용 규칙 (ProductUseComparisonRule)
|
||||
|
||||
```java
|
||||
// 대표소유자성명에 "상품용" 문자열이 포함되어 있는지 확인
|
||||
String mberNm = basicInfo.getMberNm();
|
||||
if (mberNm != null && mberNm.contains("상품용")) {
|
||||
// 상품용으로 처리
|
||||
return ComparisonResult.applied("02", "상품용으로 처리되었습니다.");
|
||||
}
|
||||
```
|
||||
|
||||
### 이첩 규칙 (TransferComparisonRule)
|
||||
|
||||
```java
|
||||
// 법정동코드 앞 4자리와 사용자 조직코드 앞 4자리 비교
|
||||
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
|
||||
String userOrg4 = userOrgCd.substring(0, 4);
|
||||
|
||||
if (!legalDong4.equals(userOrg4)) {
|
||||
// 이첩으로 처리
|
||||
return ComparisonResult.applied("03", "이첩으로 처리되었습니다.");
|
||||
}
|
||||
```
|
||||
|
||||
## API 응답 데이터 활용
|
||||
|
||||
`ComparisonContext`를 통해 다음 데이터에 접근할 수 있습니다:
|
||||
|
||||
```java
|
||||
// 기존 과태료 대상 데이터
|
||||
CarFfnlgTrgtVO existingData = context.getExistingData();
|
||||
|
||||
// API 응답 - 기본 정보
|
||||
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
String mberNm = basicInfo.getMberNm(); // 대표소유자성명
|
||||
String vhrno = basicInfo.getVhrno(); // 차량번호
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode(); // 사용본거지법정동코드
|
||||
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
|
||||
// ... 기타 필드들
|
||||
|
||||
// API 응답 - 등록원부
|
||||
var ledgerInfo = context.getApiResponse().getLedgerInfo();
|
||||
|
||||
// 사용자 ID
|
||||
String userId = context.getUserId();
|
||||
```
|
||||
|
||||
## 의존성 주입
|
||||
|
||||
규칙 클래스에서 필요한 빈을 자유롭게 주입받을 수 있습니다.
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MyComparisonRule implements ComparisonRule {
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper; // Mapper
|
||||
private final UserMapper userMapper; // User 정보 필요 시
|
||||
private final SomeOtherService someService; // 다른 서비스
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 로깅
|
||||
|
||||
규칙 실행 중 상세한 로그가 자동으로 출력됩니다.
|
||||
|
||||
```
|
||||
[상품용] 규칙 실행 중... 차량번호: 12가3456
|
||||
[상품용] 상품용 감지! 차량번호: 12가3456, 소유자명: 상품용차량
|
||||
[상품용] 처리 완료! 차량번호: 12가3456
|
||||
```
|
||||
|
||||
## 테스트 방법
|
||||
|
||||
규칙을 독립적으로 테스트할 수 있습니다.
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
class InvestigationClosedComparisonRuleTest {
|
||||
|
||||
@Autowired
|
||||
private InvestigationClosedComparisonRule rule;
|
||||
|
||||
@Test
|
||||
void 말소된_차량은_내사종결로_처리된다() {
|
||||
// Given
|
||||
ComparisonContext context = ComparisonContext.builder()
|
||||
.existingData(existingData)
|
||||
.apiResponse(apiResponse)
|
||||
.userId("USER001")
|
||||
.build();
|
||||
|
||||
// When
|
||||
ComparisonResult result = rule.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isApplied());
|
||||
assertEquals("04", result.getStatusCode());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **순서 관리**: `getOrder()` 값이 중복되지 않도록 주의
|
||||
2. **트랜잭션**: 각 규칙 내에서 DB 업데이트 수행 (전체는 Service에서 @Transactional)
|
||||
3. **예외 처리**: 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
|
||||
4. **null 체크**: API 응답 데이터는 항상 null 체크 필수
|
||||
5. **로깅**: DEBUG 레벨로 상세한 로그 남기기
|
||||
|
||||
## 문의
|
||||
|
||||
추가 비교 규칙이 필요하거나 질문이 있으면 개발팀에 문의하세요.
|
||||
@ -0,0 +1,97 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonContext;
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonResult;
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonRule;
|
||||
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 상품용 비교 규칙
|
||||
*
|
||||
* <p>대표소유자성명(MBER_NM)에서 "상품용" 문자열을 찾아 처리합니다.</p>
|
||||
*
|
||||
* <p>적용 조건:</p>
|
||||
* <ul>
|
||||
* <li>자동차 기본 사항 조회 대표소유자성명에 "상품용" 문자열 포함</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>처리 내용:</p>
|
||||
* <ul>
|
||||
* <li>TASK_PRCS_STTS_CD = 02 (상품용)</li>
|
||||
* <li>TASK_PRCS_YMD = 현재 날짜</li>
|
||||
* <li>CAR_BSC_MTTR_INQ_FLNM = 대표소유자성명</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ProductUseComparisonRule implements ComparisonRule {
|
||||
|
||||
private static final String STATUS_CODE = "02"; // 상품용
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper;
|
||||
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
|
||||
// API 응답 데이터 유효성 검사
|
||||
if (context.getApiResponse().getBasicInfo() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
|
||||
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
// 대표소유자성명 추출
|
||||
String mberNm = context.getApiResponse()
|
||||
.getBasicInfo()
|
||||
.getRecord()
|
||||
.get(0)
|
||||
.getMberNm();
|
||||
|
||||
// 상품용 체크
|
||||
if (mberNm == null || !mberNm.contains("상품용")) {
|
||||
log.debug("[{}] 상품용에 해당하지 않습니다. 차량번호: {}, 소유자명: {}", getRuleName(), vhclno, mberNm);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
log.info("[{}] 상품용 감지! 차량번호: {}, 소유자명: {}", getRuleName(), vhclno, mberNm);
|
||||
|
||||
// 업무 처리 상태 업데이트
|
||||
CarFfnlgTrgtVO updateData = context.getExistingData();
|
||||
updateData.setTaskPrcsSttsCd(STATUS_CODE);
|
||||
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
updateData.setCarBscMttrInqFlnm(mberNm); // 소유자명 저장
|
||||
updateData.setCarBscMttrInqSggCd(null); // 이첩 필드는 null
|
||||
updateData.setCarBscMttrInqSggNm(null);
|
||||
|
||||
int updateResult = mapper.update(updateData);
|
||||
|
||||
if (updateResult > 0) {
|
||||
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.applied(STATUS_CODE, "상품용으로 처리되었습니다.");
|
||||
} else {
|
||||
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
|
||||
throw new RuntimeException(String.format("상품용 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() {
|
||||
return "상품용";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 10; // 상품용은 가장 먼저 체크 (낮은 순서)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonContext;
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonResult;
|
||||
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonRule;
|
||||
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import go.kr.project.system.user.mapper.UserMapper;
|
||||
import go.kr.project.system.user.model.SystemUserVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 이첩 비교 규칙
|
||||
*
|
||||
* <p>사용본거지법정동코드 앞 4자리와 사용자 조직코드 앞 4자리를 비교하여 처리합니다.</p>
|
||||
*
|
||||
* <p>적용 조건:</p>
|
||||
* <ul>
|
||||
* <li>자동차 기본 사항 조회 사용본거지법정동코드(USE_STRNGHLD_LEGALDONG_CODE) 앞 4자리</li>
|
||||
* <li>사용자(tb_user) ORG_CD 앞 4자리</li>
|
||||
* <li>위 두 값이 다를 경우</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>처리 내용:</p>
|
||||
* <ul>
|
||||
* <li>TASK_PRCS_STTS_CD = 03 (이첩)</li>
|
||||
* <li>TASK_PRCS_YMD = 현재 날짜</li>
|
||||
* <li>CAR_BSC_MTTR_INQ_SGG_CD = 사용본거지법정동코드 앞 5자리</li>
|
||||
* <li>CAR_BSC_MTTR_INQ_SGG_NM = tb_sgg_cd 테이블에서 조회한 시군구명</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class TransferComparisonRule implements ComparisonRule {
|
||||
|
||||
private static final String STATUS_CODE = "03"; // 이첩
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
private final CarFfnlgTrgtMapper mapper;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
|
||||
// API 응답 데이터 유효성 검사
|
||||
if (context.getApiResponse().getBasicInfo() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord() == null ||
|
||||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
|
||||
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
go.kr.project.api.model.response.BasicResponse.Record basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkLegalDongCodeMismatch(context, basicInfo, vhclno)) {
|
||||
return processTransfer(context, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 향후 추가될 이첩 조건들 (예시)
|
||||
// if (checkOtherTransferCondition(context, basicInfo, vhclno)) {
|
||||
// return processTransfer(context, basicInfo, vhclno, "다른 조건");
|
||||
// }
|
||||
|
||||
// 모든 이첩 조건에 해당하지 않음
|
||||
log.debug("[{}] 모든 이첩 조건에 해당하지 않습니다. 차량번호: {}", getRuleName(), vhclno);
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
/**
|
||||
* 조건1: 법정동코드 불일치 체크
|
||||
* 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
|
||||
*/
|
||||
private boolean checkLegalDongCodeMismatch(ComparisonContext context,
|
||||
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
|
||||
String vhclno) {
|
||||
// 사용본거지법정동코드 추출
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
|
||||
|
||||
if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) {
|
||||
log.debug("[{}][조건1] 법정동코드가 없거나 길이 부족. 차량번호: {}", getRuleName(), vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 사용자 정보 조회
|
||||
SystemUserVO userInfo = userMapper.selectUser(context.getUserId());
|
||||
if (userInfo == null || userInfo.getOrgCd() == null) {
|
||||
log.debug("[{}][조건1] 사용자 정보 없음. 차량번호: {}", getRuleName(), vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 법정동코드 앞 4자리와 사용자 조직코드 앞 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)) {
|
||||
log.debug("[{}][조건1] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
getRuleName(), vhclno, legalDong4, userOrg4);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("[{}][조건1] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
getRuleName(), vhclno, legalDong4, userOrg4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이첩 처리 (공통 로직)
|
||||
*/
|
||||
private ComparisonResult processTransfer(ComparisonContext context,
|
||||
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
|
||||
String vhclno,
|
||||
String reason) {
|
||||
log.info("[{}] 이첩 감지! 차량번호: {}, 사유: {}", getRuleName(), vhclno, reason);
|
||||
|
||||
// 사용본거지법정동코드 추출
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
|
||||
|
||||
// 시군구 코드 (법정동코드 앞 5자리)
|
||||
String sggCd = (useStrnghldLegaldongCode != null && useStrnghldLegaldongCode.length() >= 5)
|
||||
? useStrnghldLegaldongCode.substring(0, 5)
|
||||
: (useStrnghldLegaldongCode != null ? useStrnghldLegaldongCode : "");
|
||||
|
||||
// 시군구명 조회
|
||||
String sggNm = mapper.selectSggNmBySggCd(sggCd);
|
||||
if (sggNm == null || sggNm.isEmpty()) {
|
||||
log.warn("[{}] 시군구명 조회 실패. 시군구코드: {} (빈 문자열로 처리)", getRuleName(), sggCd);
|
||||
sggNm = ""; // 시군구명이 없어도 이첩 처리는 진행
|
||||
}
|
||||
|
||||
// 업무 처리 상태 업데이트
|
||||
CarFfnlgTrgtVO updateData = context.getExistingData();
|
||||
updateData.setTaskPrcsSttsCd(STATUS_CODE);
|
||||
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
updateData.setCarBscMttrInqFlnm(null); // 상품용 필드는 null
|
||||
updateData.setCarBscMttrInqSggCd(sggCd); // 시군구 코드 저장
|
||||
updateData.setCarBscMttrInqSggNm(sggNm); // 시군구명 저장
|
||||
|
||||
int updateResult = mapper.update(updateData);
|
||||
|
||||
if (updateResult > 0) {
|
||||
log.info("[{}] 처리 완료! 차량번호: {}, 시군구: {}({})", getRuleName(), vhclno, sggNm, sggCd);
|
||||
return ComparisonResult.applied(STATUS_CODE, String.format("이첩으로 처리되었습니다. (시군구: %s)", sggNm));
|
||||
} else {
|
||||
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
|
||||
throw new RuntimeException(String.format("이첩 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() {
|
||||
return "이첩";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 20; // 상품용 다음으로 체크
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
# 비교 로직 구현 방법 가이드
|
||||
|
||||
## 📌 두 가지 구현 방법 제공
|
||||
|
||||
현재 프로젝트는 두 가지 비교 로직 구현 방법을 제공합니다.
|
||||
프로젝트 상황에 맞게 선택하여 사용하세요.
|
||||
|
||||
---
|
||||
|
||||
## 방법1: 일반 Service/Impl 패턴 ⭐ **추천**
|
||||
|
||||
### 특징
|
||||
- ✅ **간단하고 직관적** - 학습 곡선 낮음
|
||||
- ✅ **명확한 코드 흐름** - 순차적 실행으로 이해하기 쉬움
|
||||
- ✅ **빠른 개발** - 메서드 하나만 추가하면 됨
|
||||
- ✅ **디버깅 쉬움** - 코드 추적이 직관적
|
||||
|
||||
### 구조
|
||||
```
|
||||
service/
|
||||
├── ComparisonService.java # 인터페이스
|
||||
└── impl/
|
||||
└── ComparisonServiceImpl.java # 구현체
|
||||
```
|
||||
|
||||
### 새로운 비교 조건 추가 방법
|
||||
|
||||
#### 1단계: ComparisonServiceImpl에 private 메서드 추가
|
||||
|
||||
```java
|
||||
/**
|
||||
* 4. 새로운 비교 조건 (예: 장기미검차량)
|
||||
*
|
||||
* <p>조건: 검사 유효기간 종료일로부터 1년 이상 경과</p>
|
||||
* <p>처리: TASK_PRCS_STTS_CD = 05</p>
|
||||
*/
|
||||
private String checkLongTermUninspected(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String insptValidPdEndde = basicInfo.getInsptValidPdEndde(); // 검사유효기간종료일자
|
||||
|
||||
// 조건 체크 로직
|
||||
if (insptValidPdEndde == null || insptValidPdEndde.isEmpty()) {
|
||||
log.debug("[장기미검] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1년 경과 여부 확인 로직
|
||||
LocalDate endDate = LocalDate.parse(insptValidPdEndde, DATE_FORMATTER);
|
||||
LocalDate oneYearAgo = LocalDate.now().minusYears(1);
|
||||
|
||||
if (endDate.isAfter(oneYearAgo)) {
|
||||
log.debug("[장기미검] 조건 미충족. 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[장기미검] 조건 충족! 차량번호: {}, 종료일: {}", vhclno, insptValidPdEndde);
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("05"); // 장기미검
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[장기미검] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[장기미검] 처리 완료! 차량번호: {}", vhclno);
|
||||
return "05";
|
||||
}
|
||||
```
|
||||
|
||||
#### 2단계: executeComparison() 메서드에 호출 추가
|
||||
|
||||
```java
|
||||
@Override
|
||||
public String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
|
||||
// ... 기존 코드 ...
|
||||
|
||||
// ========== 3. 향후 추가될 비교 로직들 ==========
|
||||
String longTermResult = checkLongTermUninspected(existingData, basicInfo);
|
||||
if (longTermResult != null) {
|
||||
log.info("========== 비교 로직 종료 (장기미검): {} ==========", vhclno);
|
||||
return longTermResult;
|
||||
}
|
||||
|
||||
// ... 나머지 코드 ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 끝! 매우 간단합니다.
|
||||
|
||||
### 파일 위치
|
||||
- `service/ComparisonService.java`
|
||||
- `service/impl/ComparisonServiceImpl.java`
|
||||
|
||||
### 실행 순서
|
||||
1. 상품용 체크 → 2. 이첩 체크 → 3. 새로운 조건들...
|
||||
- **순서대로 실행**되며, 하나라도 조건이 맞으면 즉시 종료
|
||||
|
||||
---
|
||||
|
||||
## 방법2: Chain of Responsibility 패턴
|
||||
|
||||
### 특징
|
||||
- ✅ **확장성 우수** - 규칙 추가/삭제가 독립적
|
||||
- ✅ **테스트 용이** - 각 규칙을 독립적으로 테스트 가능
|
||||
- ✅ **순서 제어** - getOrder()로 실행 순서 명확히 제어
|
||||
- ❌ **복잡한 구조** - 학습 곡선 높음
|
||||
- ❌ **파일 분산** - 여러 파일을 확인해야 함
|
||||
|
||||
### 구조
|
||||
```
|
||||
comparison/
|
||||
├── ComparisonRule.java # 규칙 인터페이스
|
||||
├── ComparisonContext.java # 데이터 컨테이너
|
||||
├── ComparisonResult.java # 결과 객체
|
||||
├── ComparisonRuleProcessor.java # 체인 관리자
|
||||
└── rules/ # 규칙 구현체들
|
||||
├── ProductUseComparisonRule.java
|
||||
├── TransferComparisonRule.java
|
||||
└── ... (새 규칙 파일들)
|
||||
```
|
||||
|
||||
### 새로운 비교 조건 추가 방법
|
||||
|
||||
#### 1단계: rules/ 폴더에 새 클래스 생성
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LongTermUninspectedComparisonRule implements ComparisonRule {
|
||||
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
// 비교 로직 작성
|
||||
if (/* 조건 충족 */) {
|
||||
// DB 업데이트
|
||||
return ComparisonResult.applied("05", "장기미검으로 처리");
|
||||
}
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() { return "장기미검"; }
|
||||
|
||||
@Override
|
||||
public int getOrder() { return 40; } // 실행 순서
|
||||
}
|
||||
```
|
||||
|
||||
#### 끝! 자동으로 등록됩니다.
|
||||
|
||||
### 파일 위치
|
||||
- `comparison/` 패키지 전체
|
||||
|
||||
---
|
||||
|
||||
## 🎯 어떤 방법을 선택해야 할까?
|
||||
|
||||
### 방법1 선택 (Service/Impl) - 다음과 같은 경우 추천
|
||||
|
||||
- ✅ **간단한 프로젝트** - 비교 조건이 10개 이하
|
||||
- ✅ **빠른 개발 필요** - 당장 구현해야 할 때
|
||||
- ✅ **팀원 경험 부족** - 디자인 패턴에 익숙하지 않은 경우
|
||||
- ✅ **유지보수 단순** - 한 파일에서 모든 로직 확인 가능
|
||||
|
||||
### 방법2 선택 (Chain of Responsibility) - 다음과 같은 경우 추천
|
||||
|
||||
- ✅ **복잡한 프로젝트** - 비교 조건이 10개 이상
|
||||
- ✅ **장기 유지보수** - 규칙이 자주 추가/변경될 것으로 예상
|
||||
- ✅ **팀 규모 큽** - 여러 개발자가 동시에 작업
|
||||
- ✅ **테스트 중요** - 각 규칙을 독립적으로 테스트해야 함
|
||||
|
||||
---
|
||||
|
||||
## 💡 현재 설정 변경 방법
|
||||
|
||||
`CarFfnlgTrgtServiceImpl.java`의 `executeComparisonLogic()` 메서드에서 주석을 변경하면 됩니다.
|
||||
|
||||
```java
|
||||
private String executeComparisonLogic(...) {
|
||||
// 방법1 사용하려면: (현재 설정)
|
||||
return executeWithServicePattern(existingData, apiResponse, userId);
|
||||
|
||||
// 방법2 사용하려면:
|
||||
// return executeWithChainPattern(existingData, apiResponse, userId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 비교표
|
||||
|
||||
| 항목 | 방법1 (Service/Impl) | 방법2 (Chain of Responsibility) |
|
||||
|------|---------------------|--------------------------------|
|
||||
| **구현 난이도** | ⭐ 쉬움 | ⭐⭐⭐ 어려움 |
|
||||
| **학습 시간** | 10분 | 1시간+ |
|
||||
| **추가 시간** | 5분 | 15분 |
|
||||
| **코드 가독성** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| **확장성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **테스트 용이성** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **파일 개수** | 2개 | N+4개 |
|
||||
| **디버깅** | ⭐⭐⭐⭐⭐ 쉬움 | ⭐⭐⭐ 보통 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 빠른 시작
|
||||
|
||||
### 방법1로 시작하기 (추천)
|
||||
1. `ComparisonServiceImpl.java` 열기
|
||||
2. 기존 메서드 참고하여 새 메서드 추가
|
||||
3. `executeComparison()`에서 호출
|
||||
4. 끝!
|
||||
|
||||
### 방법2로 시작하기
|
||||
1. `comparison/README.md` 읽기
|
||||
2. `rules/` 폴더에 새 규칙 클래스 생성
|
||||
3. `@Component` 붙이기
|
||||
4. 끝!
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
1. **두 방법을 동시에 사용하지 마세요** - 하나만 선택
|
||||
2. **트랜잭션 관리** - 각 비교 메서드는 DB 업데이트를 직접 수행
|
||||
3. **예외 처리** - 업데이트 실패 시 RuntimeException 발생 → 전체 롤백
|
||||
4. **로깅** - 각 조건의 충족/미충족 여부를 명확히 로깅
|
||||
|
||||
---
|
||||
|
||||
## 📞 문의
|
||||
|
||||
추가 질문이나 제안사항은 개발팀에 문의하세요.
|
||||
@ -0,0 +1,32 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.service;
|
||||
|
||||
import go.kr.project.api.model.VehicleApiResponseVO;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
|
||||
/**
|
||||
* 과태료 대상 비교 서비스
|
||||
*
|
||||
* <p>차량 정보를 API 응답과 비교하여 상태를 자동으로 분류합니다.</p>
|
||||
*/
|
||||
public interface ComparisonService {
|
||||
|
||||
/**
|
||||
* 비교 로직을 실행하고 업무 처리 상태를 업데이트합니다.
|
||||
*
|
||||
* <p>실행 순서:</p>
|
||||
* <ol>
|
||||
* <li>상품용 체크</li>
|
||||
* <li>이첩 체크</li>
|
||||
* <li>내사종결 체크 (향후 추가)</li>
|
||||
* <li>기타 비교 로직들...</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>한 가지 조건이라도 만족하면 즉시 해당 상태로 업데이트하고 종료합니다.</p>
|
||||
*
|
||||
* @param existingData 기존 과태료 대상 데이터
|
||||
* @param apiResponse API 응답 데이터
|
||||
* @param userId 사용자 ID
|
||||
* @return 처리 상태 코드 (02=상품용, 03=이첩, 04=내사종결, null=해당없음)
|
||||
*/
|
||||
String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId);
|
||||
}
|
||||
@ -0,0 +1,262 @@
|
||||
package go.kr.project.carInspectionPenalty.registration.service.impl;
|
||||
|
||||
import go.kr.project.api.model.VehicleApiResponseVO;
|
||||
import go.kr.project.api.model.response.BasicResponse;
|
||||
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
|
||||
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
|
||||
import go.kr.project.carInspectionPenalty.registration.service.ComparisonService;
|
||||
import go.kr.project.system.user.mapper.UserMapper;
|
||||
import go.kr.project.system.user.model.SystemUserVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 과태료 대상 비교 서비스 구현체
|
||||
*
|
||||
* <p>각 비교 로직을 독립적인 메서드로 분리하여 관리합니다.</p>
|
||||
* <p>새로운 비교 조건 추가 시: private 메서드를 추가하고 executeComparison()에서 호출</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ComparisonServiceImpl implements ComparisonService {
|
||||
|
||||
private final CarFfnlgTrgtMapper carFfnlgTrgtMapper;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
/**
|
||||
* 비교 로직 메인 메서드
|
||||
*
|
||||
* <p>순차적으로 각 비교 메서드를 실행하고, 하나라도 적용되면 즉시 종료합니다.</p>
|
||||
*/
|
||||
@Override
|
||||
public String executeComparison(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
log.info("========== 비교 로직 시작: {} ==========", vhclno);
|
||||
|
||||
// API 응답 데이터 유효성 검사
|
||||
if (!isValidApiResponse(apiResponse)) {
|
||||
log.warn("API 응답 데이터가 유효하지 않습니다. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
BasicResponse.Record basicInfo = apiResponse.getBasicInfo().getRecord().get(0);
|
||||
|
||||
// ========== 1. 상품용 체크 ==========
|
||||
String productUseResult = checkProductUse(existingData, basicInfo);
|
||||
if (productUseResult != null) {
|
||||
log.info("========== 비교 로직 종료 (상품용): {} ==========", vhclno);
|
||||
return productUseResult;
|
||||
}
|
||||
|
||||
// ========== 2. 이첩 체크 ==========
|
||||
String transferResult = checkTransfer(existingData, basicInfo, userId);
|
||||
if (transferResult != null) {
|
||||
log.info("========== 비교 로직 종료 (이첩): {} ==========", vhclno);
|
||||
return transferResult;
|
||||
}
|
||||
|
||||
// ========== 3. 향후 추가될 비교 로직들 ==========
|
||||
// String investigationClosedResult = checkInvestigationClosed(existingData, basicInfo);
|
||||
// if (investigationClosedResult != null) {
|
||||
// return investigationClosedResult;
|
||||
// }
|
||||
|
||||
// 모든 비교 로직에 해당하지 않음
|
||||
log.info("========== 비교 로직 종료 (정상): {} ==========", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 비교 로직 메서드들
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 1. 상품용 체크
|
||||
*
|
||||
* <p>조건: 대표소유자성명(MBER_NM)에 "상품용" 문자열 포함</p>
|
||||
* <p>처리: TASK_PRCS_STTS_CD = 02, CAR_BSC_MTTR_INQ_FLNM = 소유자명</p>
|
||||
*
|
||||
* @return 02 (적용됨) 또는 null (미적용)
|
||||
*/
|
||||
private String checkProductUse(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String mberNm = basicInfo.getMberNm(); // 대표소유자성명
|
||||
|
||||
// 조건 체크
|
||||
if (mberNm == null || !mberNm.contains("상품용")) {
|
||||
log.debug("[상품용] 조건 미충족. 차량번호: {}, 소유자명: {}", vhclno, mberNm);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[상품용] 조건 충족! 차량번호: {}, 소유자명: {}", vhclno, mberNm);
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("02"); // 상품용
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(mberNm); // 소유자명 저장
|
||||
existingData.setCarBscMttrInqSggCd(null);
|
||||
existingData.setCarBscMttrInqSggNm(null);
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[상품용] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[상품용] 처리 완료! 차량번호: {}", vhclno);
|
||||
return "02";
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 이첩 체크 (OR 로직: 여러 조건 중 하나라도 만족하면 이첩)
|
||||
*
|
||||
* <p>처리: TASK_PRCS_STTS_CD = 03, CAR_BSC_MTTR_INQ_SGG_CD = 시군구코드, CAR_BSC_MTTR_INQ_SGG_NM = 시군구명</p>
|
||||
*
|
||||
* @return 03 (적용됨) 또는 null (미적용)
|
||||
*/
|
||||
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 향후 추가될 이첩 조건들 (예시)
|
||||
// if (checkTransferCondition2_OtherReason(basicInfo, userId, vhclno)) {
|
||||
// return processTransfer(existingData, basicInfo, vhclno, "다른 사유");
|
||||
// }
|
||||
|
||||
// 모든 이첩 조건에 해당하지 않음
|
||||
log.debug("[이첩] 모든 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이첩 조건1: 법정동코드 불일치
|
||||
* 사용본거지법정동코드 앞 4자리 != 사용자 조직코드 앞 4자리
|
||||
*/
|
||||
private boolean checkTransferCondition1_LegalDongMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
|
||||
|
||||
// 법정동코드 유효성 검사
|
||||
if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) {
|
||||
log.debug("[이첩][조건1] 법정동코드 없음. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 사용자 정보 조회
|
||||
SystemUserVO userInfo = userMapper.selectUser(userId);
|
||||
if (userInfo == null || userInfo.getOrgCd() == null) {
|
||||
log.debug("[이첩][조건1] 사용자 정보 없음. 사용자ID: {}", userId);
|
||||
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)) {
|
||||
log.debug("[이첩][조건1] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
vhclno, legalDong4, userOrg4);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("[이첩][조건1] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
vhclno, legalDong4, userOrg4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이첩 처리 (공통 로직)
|
||||
*/
|
||||
private String processTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo,
|
||||
String vhclno, String reason) {
|
||||
log.info("[이첩] 조건 충족! 차량번호: {}, 사유: {}", vhclno, reason);
|
||||
|
||||
// 시군구 코드 및 시군구명 조회
|
||||
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
|
||||
String sggCd = (useStrnghldLegaldongCode != null && useStrnghldLegaldongCode.length() >= 5)
|
||||
? useStrnghldLegaldongCode.substring(0, 5)
|
||||
: (useStrnghldLegaldongCode != null ? useStrnghldLegaldongCode : "");
|
||||
|
||||
String sggNm = carFfnlgTrgtMapper.selectSggNmBySggCd(sggCd);
|
||||
if (sggNm == null || sggNm.isEmpty()) {
|
||||
log.warn("[이첩] 시군구명 조회 실패. 시군구코드: {}", sggCd);
|
||||
sggNm = "";
|
||||
}
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("03"); // 이첩
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(null);
|
||||
existingData.setCarBscMttrInqSggCd(sggCd); // 시군구 코드
|
||||
existingData.setCarBscMttrInqSggNm(sggNm); // 시군구명
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[이첩] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[이첩] 처리 완료! 차량번호: {}, 시군구: {}({}), 사유: {}", vhclno, sggNm, sggCd, reason);
|
||||
return "03";
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 내사종결 체크 (예시 - 향후 추가)
|
||||
*
|
||||
* <p>조건: 차량이 말소된 경우 등</p>
|
||||
* <p>처리: TASK_PRCS_STTS_CD = 04</p>
|
||||
*
|
||||
* @return 04 (적용됨) 또는 null (미적용)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private String checkInvestigationClosed(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String ersrRegistSeCode = basicInfo.getErsrRegistSeCode(); // 말소등록구분코드
|
||||
|
||||
// 조건 체크: 말소된 차량인지
|
||||
if (ersrRegistSeCode == null || ersrRegistSeCode.isEmpty()) {
|
||||
log.debug("[내사종결] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결] 조건 충족! 차량번호: {}, 말소구분: {}", vhclno, ersrRegistSeCode);
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setTaskPrcsSttsCd("04"); // 내사종결
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
// 필요한 추가 필드 설정
|
||||
|
||||
int updateCount = carFfnlgTrgtMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new RuntimeException(String.format("[내사종결] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[내사종결] 처리 완료! 차량번호: {}", vhclno);
|
||||
return "04";
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 유틸리티 메서드
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* API 응답 데이터 유효성 검사
|
||||
*/
|
||||
private boolean isValidApiResponse(VehicleApiResponseVO apiResponse) {
|
||||
return apiResponse != null
|
||||
&& apiResponse.getBasicInfo() != null
|
||||
&& apiResponse.getBasicInfo().getRecord() != null
|
||||
&& !apiResponse.getBasicInfo().getRecord().isEmpty();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,263 @@
|
||||
# 이첩 조건 추가 방법 (OR 구조)
|
||||
|
||||
## 📌 현재 구조
|
||||
|
||||
이첩 비교 로직은 **OR 구조**로 되어 있습니다.
|
||||
- **여러 조건 중 하나라도 만족하면** 이첩으로 처리
|
||||
- 조건을 순차적으로 체크하다가 **하나라도 true가 나오면 즉시 이첩 처리**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 새로운 이첩 조건 추가하기
|
||||
|
||||
### 방법1: Service/Impl 패턴 (간단) ⭐ 추천
|
||||
|
||||
`ComparisonServiceImpl.java` 파일 수정
|
||||
|
||||
#### 1단계: 조건 체크 메서드 추가
|
||||
|
||||
```java
|
||||
/**
|
||||
* 이첩 조건2: 차량 소유자 주소 불일치 (예시)
|
||||
* 조건: 차량 등록지와 소유자 실거주지가 다른 경우
|
||||
*/
|
||||
private boolean checkTransferCondition2_AddressMismatch(BasicResponse.Record basicInfo, String userId, String vhclno) {
|
||||
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm(); // 사용본거지주소명
|
||||
String ownerAdresNm = basicInfo.getOwnerAdresNm(); // 소유자주소명
|
||||
|
||||
// 주소 유효성 검사
|
||||
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
|
||||
log.debug("[이첩][조건2] 주소 정보 없음. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 주소 일치 여부 확인 (시/도 단위 비교 등)
|
||||
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
|
||||
log.debug("[이첩][조건2] 주소 일치. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("[이첩][조건2] 주소 불일치! 차량번호: {}, 사용본거지: {}, 소유자주소: {}",
|
||||
vhclno, useStrnghldAdresNm, ownerAdresNm);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2단계: checkTransfer() 메서드에 조건 추가
|
||||
|
||||
```java
|
||||
private String checkTransfer(CarFfnlgTrgtVO existingData, BasicResponse.Record basicInfo, String userId) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkTransferCondition1_LegalDongMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 주소 불일치 (새로 추가!)
|
||||
if (checkTransferCondition2_AddressMismatch(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "주소 불일치");
|
||||
}
|
||||
|
||||
// 조건3: 향후 추가될 조건들...
|
||||
// if (checkTransferCondition3_XXX(basicInfo, userId, vhclno)) {
|
||||
// return processTransfer(existingData, basicInfo, vhclno, "XXX");
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
#### 끝! 매우 간단합니다.
|
||||
|
||||
---
|
||||
|
||||
### 방법2: Chain of Responsibility 패턴
|
||||
|
||||
`TransferComparisonRule.java` 파일 수정
|
||||
|
||||
#### 1단계: 조건 체크 메서드 추가
|
||||
|
||||
```java
|
||||
/**
|
||||
* 조건2: 주소 불일치 체크
|
||||
*/
|
||||
private boolean checkAddressMismatch(ComparisonContext context,
|
||||
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
|
||||
String vhclno) {
|
||||
String useStrnghldAdresNm = basicInfo.getUseStrnghldAdresNm();
|
||||
String ownerAdresNm = basicInfo.getOwnerAdresNm();
|
||||
|
||||
if (useStrnghldAdresNm == null || ownerAdresNm == null) {
|
||||
log.debug("[{}][조건2] 주소 정보 없음. 차량번호: {}", getRuleName(), vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useStrnghldAdresNm.equals(ownerAdresNm)) {
|
||||
log.debug("[{}][조건2] 주소 일치. 차량번호: {}", getRuleName(), vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("[{}][조건2] 주소 불일치! 차량번호: {}", getRuleName(), vhclno);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2단계: execute() 메서드에 조건 추가
|
||||
|
||||
```java
|
||||
@Override
|
||||
public ComparisonResult execute(ComparisonContext context) {
|
||||
String vhclno = context.getVhclno();
|
||||
|
||||
// ... API 유효성 검사 ...
|
||||
|
||||
var basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
|
||||
|
||||
// ========== 이첩 조건들 (OR 로직) ==========
|
||||
|
||||
// 조건1: 법정동코드 불일치
|
||||
if (checkLegalDongCodeMismatch(context, basicInfo, vhclno)) {
|
||||
return processTransfer(context, basicInfo, vhclno, "법정동코드 불일치");
|
||||
}
|
||||
|
||||
// 조건2: 주소 불일치 (새로 추가!)
|
||||
if (checkAddressMismatch(context, basicInfo, vhclno)) {
|
||||
return processTransfer(context, basicInfo, vhclno, "주소 불일치");
|
||||
}
|
||||
|
||||
return ComparisonResult.notApplied();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 현재 이첩 조건 목록
|
||||
|
||||
### 조건1: 법정동코드 불일치 ✅ 구현됨
|
||||
- **체크 항목**: 사용본거지법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리
|
||||
- **이첩 기준**: 두 값이 다르면 이첩
|
||||
|
||||
### 조건2: (예시) 주소 불일치
|
||||
- **체크 항목**: 사용본거지주소 vs 소유자주소
|
||||
- **이첩 기준**: 주소가 다르면 이첩
|
||||
|
||||
### 조건3: (예시) 기타 조건들...
|
||||
- 필요에 따라 추가
|
||||
|
||||
---
|
||||
|
||||
## 💡 동작 방식
|
||||
|
||||
```java
|
||||
// 이첩 체크 시작
|
||||
checkTransfer(...)
|
||||
↓
|
||||
조건1 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
조건2 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
조건3 체크 → TRUE → 즉시 이첩 처리 후 return "03"
|
||||
↓ FALSE
|
||||
모든 조건 FALSE → return null (이첩 아님)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 로그 예시
|
||||
|
||||
### 조건1에 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 불일치! 차량번호: 12가3456, 법정동: 1100, 조직: 4100
|
||||
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 법정동코드 불일치
|
||||
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 법정동코드 불일치
|
||||
```
|
||||
|
||||
### 조건2에 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456, 법정동: 1100, 조직: 1100
|
||||
[이첩][조건2] 주소 불일치! 차량번호: 12가3456
|
||||
[이첩] 조건 충족! 차량번호: 12가3456, 사유: 주소 불일치
|
||||
[이첩] 처리 완료! 차량번호: 12가3456, 시군구: 서울특별시(11000), 사유: 주소 불일치
|
||||
```
|
||||
|
||||
### 모든 조건에 안 걸린 경우
|
||||
```
|
||||
[이첩][조건1] 법정동코드 일치. 차량번호: 12가3456
|
||||
[이첩][조건2] 주소 일치. 차량번호: 12가3456
|
||||
[이첩] 모든 조건 미충족. 차량번호: 12가3456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
1. **조건 순서**: 위에서부터 순차적으로 체크됩니다
|
||||
- 자주 걸리는 조건을 위쪽에 배치하면 성능 향상
|
||||
|
||||
2. **조건 메서드 네이밍**: `checkTransferConditionN_XXX` 형식 권장
|
||||
- 예: `checkTransferCondition1_LegalDongMismatch`
|
||||
- 예: `checkTransferCondition2_AddressMismatch`
|
||||
|
||||
3. **return 값**:
|
||||
- `true`: 이 조건에 **해당함** → 이첩 처리
|
||||
- `false`: 이 조건에 **해당 안함** → 다음 조건 체크
|
||||
|
||||
4. **processTransfer() 공통 사용**:
|
||||
- DB 업데이트는 `processTransfer()` 메서드에서 공통으로 처리
|
||||
- 각 조건에서는 `true/false`만 반환
|
||||
|
||||
---
|
||||
|
||||
## 📝 체크리스트
|
||||
|
||||
새로운 이첩 조건 추가 시 확인사항:
|
||||
|
||||
- [ ] 조건 메서드 작성 완료
|
||||
- [ ] checkTransfer()에 조건 추가 완료
|
||||
- [ ] 로그 메시지에 조건 번호 포함 (`[조건N]`)
|
||||
- [ ] null 체크 처리 완료
|
||||
- [ ] 컴파일 테스트 완료
|
||||
- [ ] 실제 데이터로 테스트 완료
|
||||
|
||||
---
|
||||
|
||||
## 🚀 빠른 템플릿
|
||||
|
||||
```java
|
||||
/**
|
||||
* 이첩 조건N: XXX
|
||||
* 조건 설명
|
||||
*/
|
||||
private boolean checkTransferConditionN_XXX(BasicResponse.Record basicInfo, String userId, String vhclno) {
|
||||
// 1. 데이터 추출
|
||||
String data1 = basicInfo.getXXX();
|
||||
String data2 = basicInfo.getYYY();
|
||||
|
||||
// 2. 유효성 검사
|
||||
if (data1 == null || data2 == null) {
|
||||
log.debug("[이첩][조건N] 데이터 없음. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 조건 체크
|
||||
if (조건_만족) {
|
||||
log.info("[이첩][조건N] 조건 충족! 차량번호: {}, 상세정보...", vhclno);
|
||||
return true; // 이첩!
|
||||
}
|
||||
|
||||
log.debug("[이첩][조건N] 조건 미충족. 차량번호: {}", vhclno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// checkTransfer()에 추가
|
||||
if (checkTransferConditionN_XXX(basicInfo, userId, vhclno)) {
|
||||
return processTransfer(existingData, basicInfo, vhclno, "XXX");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
끝!
|
||||
Loading…
Reference in New Issue