미필 초기 작업 진행중....
parent
fdcf38bb31
commit
40d8518a50
@ -0,0 +1,109 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 자동차 과태료 미필 PRN 파일 파싱 설정
|
||||
* application.yml의 car-ffnlg-prn-parse 설정을 바인딩
|
||||
*
|
||||
* hangul-byte-size 값에 따라 적절한 바이트 길이 설정을 자동으로 선택합니다.
|
||||
* - hangul-byte-size: 2 → byte-size-2 설정 사용 (EUC-KR, MS949)
|
||||
* - hangul-byte-size: 3 → byte-size-3 설정 사용 (UTF-8)
|
||||
*
|
||||
* 각 항목별로 지정된 바이트 길이만 설정 (from-to 방식이 아님)
|
||||
* 예: no: 6 (6바이트), vhclno: 14 (14바이트)
|
||||
* -1 값은 "나머지 전체"를 의미
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "car-ffnlg-prn-parse")
|
||||
@Data
|
||||
public class CarFfnlgPrnParseConfig {
|
||||
|
||||
/**
|
||||
* 파일 인코딩 (UTF-8, EUC-KR, MS949 등)
|
||||
*/
|
||||
private String encoding;
|
||||
|
||||
/**
|
||||
* 한글 문자 바이트 크기
|
||||
* 2 = EUC-KR/MS949 (2바이트)
|
||||
* 3 = UTF-8 (3바이트)
|
||||
*/
|
||||
private int hangulByteSize;
|
||||
|
||||
/**
|
||||
* 2바이트 환경 설정 (EUC-KR, MS949)
|
||||
*/
|
||||
private ByteSizeConfig byteSize2;
|
||||
|
||||
/**
|
||||
* 3바이트 환경 설정 (UTF-8)
|
||||
*/
|
||||
private ByteSizeConfig byteSize3;
|
||||
|
||||
/**
|
||||
* 바이트 크기별 설정 (first-line, second-line 포함)
|
||||
*/
|
||||
@Data
|
||||
public static class ByteSizeConfig {
|
||||
/**
|
||||
* 첫째줄 필드별 바이트 길이
|
||||
*/
|
||||
private Map<String, Integer> firstLine;
|
||||
|
||||
/**
|
||||
* 둘째줄 필드별 바이트 길이
|
||||
*/
|
||||
private Map<String, Integer> secondLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 hangul-byte-size에 해당하는 설정 가져오기
|
||||
*
|
||||
* @return ByteSizeConfig (2바이트 또는 3바이트 설정)
|
||||
*/
|
||||
private ByteSizeConfig getCurrentConfig() {
|
||||
if (hangulByteSize == 3) {
|
||||
return byteSize3;
|
||||
} else {
|
||||
// 기본값은 2바이트 (EUC-KR, MS949)
|
||||
return byteSize2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 첫째줄 필드의 바이트 길이 가져오기
|
||||
* hangul-byte-size 값에 따라 적절한 설정에서 길이를 반환합니다.
|
||||
*
|
||||
* @param fieldKey 필드 키 (예: "no", "vhclno")
|
||||
* @return 바이트 길이 (-1 = 나머지 전체)
|
||||
*/
|
||||
public int getFirstLineLength(String fieldKey) {
|
||||
ByteSizeConfig config = getCurrentConfig();
|
||||
if (config == null || config.getFirstLine() == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer length = config.getFirstLine().get(fieldKey);
|
||||
return length != null ? length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 둘째줄 필드의 바이트 길이 가져오기
|
||||
* hangul-byte-size 값에 따라 적절한 설정에서 길이를 반환합니다.
|
||||
*
|
||||
* @param fieldKey 필드 키 (예: "skip", "rrno")
|
||||
* @return 바이트 길이 (-1 = 나머지 전체)
|
||||
*/
|
||||
public int getSecondLineLength(String fieldKey) {
|
||||
ByteSizeConfig config = getCurrentConfig();
|
||||
if (config == null || config.getSecondLine() == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer length = config.getSecondLine().get(fieldKey);
|
||||
return length != null ? length : 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,572 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.controller;
|
||||
|
||||
import egovframework.constant.MessageConstants;
|
||||
import egovframework.constant.TilesConstants;
|
||||
import egovframework.util.ApiResponseUtil;
|
||||
import egovframework.util.SessionUtil;
|
||||
import egovframework.util.excel.ExcelSheetData;
|
||||
import egovframework.util.excel.SxssfExcelFile;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpExcelVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpModifiedDataVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.CarFfnlgTrgtIncmpService;
|
||||
import go.kr.project.common.model.CmmnCodeSearchVO;
|
||||
import go.kr.project.common.service.CommonCodeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 자동차 미필 과태료 대상 등록 Controller
|
||||
* 미필 과태료 대상 목록 조회, PRN 파일 업로드 기능 제공
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/carInspectionPenalty/registration-om")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Tag(name = "자동차 미필 과태료 대상 등록", description = "자동차 미필 과태료 대상 등록 및 목록 조회 API")
|
||||
public class CarFfnlgTrgtIncmpController {
|
||||
|
||||
private final CarFfnlgTrgtIncmpService service;
|
||||
private final CommonCodeService commonCodeService;
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 화면
|
||||
* @param model 모델
|
||||
* @return 목록 화면 경로
|
||||
*/
|
||||
@GetMapping("/list.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 화면", description = "미필 과태료 대상 목록 조회 화면을 제공합니다.")
|
||||
public String list(Model model) {
|
||||
log.debug("미필 과태료 대상 목록 화면 요청");
|
||||
|
||||
// 업무 처리 상태 코드 조회 (공통코드)
|
||||
CmmnCodeSearchVO taskPrcsSttsCdSearchVO = CmmnCodeSearchVO.builder()
|
||||
.searchCdGroupId("TASK_PRCS_STTS_CD")
|
||||
.searchUseYn("Y")
|
||||
.sortColumn("SORT_ORDR")
|
||||
.sortAscending(true)
|
||||
.build();
|
||||
model.addAttribute("taskPrcsSttsCdList", commonCodeService.selectCodeDetailList(taskPrcsSttsCdSearchVO));
|
||||
|
||||
// 미필 과태료 대상 구분 코드 조회 (공통코드)
|
||||
CmmnCodeSearchVO ffnlgTrgtSeCdSearchVO = CmmnCodeSearchVO.builder()
|
||||
.searchCdGroupId("FFNLG_TRGT_SE_CD")
|
||||
.searchUseYn("Y")
|
||||
.sortColumn("SORT_ORDR")
|
||||
.sortAscending(true)
|
||||
.build();
|
||||
model.addAttribute("ffnlgTrgtSeCdList", commonCodeService.selectCodeDetailList(ffnlgTrgtSeCdSearchVO));
|
||||
|
||||
return "carInspectionPenalty/registrationOm/list" + TilesConstants.BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 조회 AJAX
|
||||
* @param paramVO 검색 조건
|
||||
* @return 목록 데이터
|
||||
*/
|
||||
@PostMapping("/list.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 목록 조회", description = "미필 과태료 대상 목록을 조회하고 JSON 형식으로 반환합니다.")
|
||||
public ResponseEntity<?> listAjax(@ModelAttribute CarFfnlgTrgtIncmpVO paramVO) {
|
||||
log.debug("미필 과태료 대상 목록 조회 AJAX - 검색조건: {}", paramVO);
|
||||
|
||||
// 1. 총 개수 조회
|
||||
int totalCount = service.selectListTotalCount(paramVO);
|
||||
|
||||
// 2. totalCount 설정
|
||||
paramVO.setTotalCount(totalCount);
|
||||
|
||||
// 3. 페이징 활성화
|
||||
paramVO.setPagingYn("Y");
|
||||
|
||||
// 목록 조회
|
||||
List<CarFfnlgTrgtIncmpVO> list = service.selectList(paramVO);
|
||||
|
||||
return ApiResponseUtil.successWithGrid(list, paramVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 다운로드 (EUC-KR 텍스트)
|
||||
* 샘플 파일과 동일한 고정폭 포맷으로 생성하여 다운로드 제공합니다.
|
||||
* - 인코딩: EUC-KR (한글 2바이트)
|
||||
*/
|
||||
@GetMapping("/download.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 다운로드", description = "EUC-KR 인코딩의 고정폭 텍스트로 목록을 샘플과 동일한 포맷으로 다운로드합니다.")
|
||||
public void download(
|
||||
@ModelAttribute CarFfnlgTrgtIncmpVO paramVO,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
try {
|
||||
|
||||
// 페이징 없이 전체 조회를 위해 페이징 비활성화
|
||||
paramVO.setPagingYn("N");
|
||||
|
||||
// 서비스에서 EUC-KR 텍스트 콘텐츠 생성
|
||||
byte[] fileBytes = service.generateEucKrDownloadBytes(paramVO);
|
||||
|
||||
// EUC-KR 바이트를 UTF-8 바이트로 변환 (다운로드 시에만)
|
||||
String content = new String(fileBytes, "EUC-KR");
|
||||
byte[] utfFileBytes = content.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
String fileName = URLEncoder.encode("미필_유효기간경과_과태료부과대상_리스트.prn", "UTF-8");
|
||||
|
||||
// 응답 헤더 설정 (텍스트 파일, UTF-8 인코딩)
|
||||
response.setContentType("text/plain; charset=UTF-8");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||
response.setContentLength(utfFileBytes.length);
|
||||
|
||||
// UTF-8 바이트 스트림으로 전송
|
||||
response.getOutputStream().write(utfFileBytes);
|
||||
response.getOutputStream().flush();
|
||||
} catch (Exception e) {
|
||||
log.error("목록 다운로드 중 오류", e);
|
||||
try {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
response.getWriter().write("다운로드 처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 업로드 팝업 화면
|
||||
* @return 팝업 화면
|
||||
*/
|
||||
@GetMapping("/uploadPopup.do")
|
||||
@Operation(summary = "파일 업로드 팝업", description = "PRN 파일 업로드 팝업 화면을 제공합니다.")
|
||||
public ModelAndView uploadPopup() {
|
||||
log.debug("파일 업로드 팝업 화면 요청");
|
||||
|
||||
ModelAndView mav = new ModelAndView("carInspectionPenalty/registrationOm/uploadPopup" + TilesConstants.POPUP);
|
||||
|
||||
return mav;
|
||||
}
|
||||
|
||||
/**
|
||||
* PRN 파일 업로드 및 처리
|
||||
*
|
||||
* 주의: 파일 단위로 업로드하므로 한 건이라도 실패하면 전체 롤백 처리됨
|
||||
*
|
||||
* @param file 업로드된 PRN 파일
|
||||
* @return 처리 결과
|
||||
*/
|
||||
@PostMapping("/upload.ajax")
|
||||
@Operation(summary = "PRN 파일 업로드", description = "PRN 파일을 업로드하고 파싱하여 DB에 저장합니다. 한 건이라도 실패 시 전체 롤백됩니다.")
|
||||
public ResponseEntity<?> upload(
|
||||
@Parameter(description = "PRN 파일") @RequestParam("file") MultipartFile file) {
|
||||
|
||||
log.info("PRN 파일 업로드 요청 - 파일명: {}", file != null ? file.getOriginalFilename() : "null");
|
||||
|
||||
try {
|
||||
// 세션에서 사용자 ID 가져오기
|
||||
String rgtr = SessionUtil.getUserId();
|
||||
if (rgtr == null || rgtr.isEmpty()) {
|
||||
return ApiResponseUtil.error("로그인 정보가 없습니다.");
|
||||
}
|
||||
|
||||
// UTF-8 파일을 EUC-KR로 변환 (시스템은 EUC-KR 기준으로 처리)
|
||||
MultipartFile convertedFile = convertUtf8ToEucKr(file);
|
||||
|
||||
// 파일 업로드 및 파싱 (한 건이라도 실패 시 전체 롤백)
|
||||
Map<String, Object> result = service.uploadAndParsePrnFile(convertedFile, rgtr);
|
||||
|
||||
int successCount = (int) result.get("successCount");
|
||||
int failCount = (int) result.get("failCount");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> errorMessages = (List<String>) result.get("errorMessages");
|
||||
|
||||
if (failCount == 0 && successCount > 0) {
|
||||
// 모든 데이터가 성공적으로 저장됨
|
||||
String message = String.format("파일 업로드가 완료되었습니다.\n\n성공: %d건", successCount);
|
||||
return ApiResponseUtil.success(result, message);
|
||||
} else if (successCount > 0) {
|
||||
// 일부 성공, 일부 실패
|
||||
String message = String.format("파일 업로드가 완료되었습니다.\n\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
return ApiResponseUtil.success(result, message);
|
||||
} else {
|
||||
// 모두 실패
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (!errorMessages.isEmpty()) {
|
||||
for (String errorMsg : errorMessages) {
|
||||
message.append(errorMsg).append("\n");
|
||||
}
|
||||
} else {
|
||||
message.append("파일 업로드 중 오류가 발생했습니다.");
|
||||
}
|
||||
return ApiResponseUtil.error(message.toString());
|
||||
}
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// 데이터 처리 중 오류 발생 - 전체 롤백됨
|
||||
log.error("PRN 파일 업로드 중 오류 발생 - 전체 롤백", e);
|
||||
return ApiResponseUtil.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
// 예상치 못한 오류
|
||||
log.error("PRN 파일 업로드 중 예상치 못한 오류 발생", e);
|
||||
return ApiResponseUtil.error("파일 업로드 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 상세 조회
|
||||
* @param carFfnlgTrgtIncmpId 미필 과태료 대상 ID
|
||||
* @return 상세 정보
|
||||
*/
|
||||
@GetMapping("/selectOne.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 상세 조회", description = "미필 과태료 대상 상세 정보를 조회합니다.")
|
||||
public ResponseEntity<?> selectOne(
|
||||
@Parameter(description = "미필 과태료 대상 ID") @RequestParam String carFfnlgTrgtIncmpId) {
|
||||
|
||||
log.debug("미필 과태료 대상 상세 조회 - ID: {}", carFfnlgTrgtIncmpId);
|
||||
|
||||
try {
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
|
||||
|
||||
CarFfnlgTrgtIncmpVO result = service.selectOne(vo);
|
||||
|
||||
if (result != null) {
|
||||
return ApiResponseUtil.success(result, "조회 성공");
|
||||
} else {
|
||||
return ApiResponseUtil.error("조회된 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 상세 조회 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("조회 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 삭제 (논리삭제)
|
||||
* @param carFfnlgTrgtIncmpId 미필 과태료 대상 ID
|
||||
* @return 삭제 결과
|
||||
*/
|
||||
@PostMapping("/delete.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 삭제", description = "미필 과태료 대상을 삭제(논리삭제)합니다.")
|
||||
public ResponseEntity<?> delete(
|
||||
@Parameter(description = "미필 과태료 대상 ID") @RequestParam String carFfnlgTrgtIncmpId) {
|
||||
|
||||
log.info("미필 과태료 대상 삭제 요청 - ID: {}", carFfnlgTrgtIncmpId);
|
||||
|
||||
try {
|
||||
String dltr = SessionUtil.getUserId();
|
||||
if (dltr == null || dltr.isEmpty()) {
|
||||
return ApiResponseUtil.error("로그인 정보가 없습니다.");
|
||||
}
|
||||
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
|
||||
vo.setDltr(dltr);
|
||||
|
||||
int result = service.delete(vo);
|
||||
|
||||
if (result > 0) {
|
||||
return ApiResponseUtil.success(MessageConstants.Common.DELETE_SUCCESS);
|
||||
} else {
|
||||
return ApiResponseUtil.error(MessageConstants.Common.DELETE_ERROR);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 삭제 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택된 목록에 대해 API 호출 및 기본정보/등록원부 비교
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
* @param targetList 선택된 미필 과태료 대상 목록
|
||||
* @return 비교 결과
|
||||
*/
|
||||
@PostMapping("/compareWithApi.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "API 호출 및 데이터 비교", description = "선택된 목록에 대해 차량 API를 호출하고 기본정보 및 등록원부와 비교합니다. 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일")
|
||||
public ResponseEntity<?> compareWithApi(@RequestBody List<Map<String, String>> targetList) {
|
||||
log.info("API 호출 및 비교 요청 (미필) - 선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);
|
||||
|
||||
try {
|
||||
Map<String, Object> resultData = service.compareWithApi(targetList);
|
||||
|
||||
int successCount = (int) resultData.get("successCount");
|
||||
int failCount = (int) resultData.get("failCount");
|
||||
String message = String.format("API 호출 및 비교 완료\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("파라미터 검증 오류", e);
|
||||
return ApiResponseUtil.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("API 호출 및 비교 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검색조건 전체 목록에 대해 API 호출 및 기본정보/등록원부 비교 (페이징 없이)
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
* @param searchParams 검색 조건 파라미터
|
||||
* @return 비교 결과
|
||||
*/
|
||||
@PostMapping("/compareWithApiAll.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "검색조건 전체 API 호출 및 데이터 비교", description = "검색조건에 해당하는 전체 목록에 대해 차량 API를 호출하고 비교합니다. 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일")
|
||||
public ResponseEntity<?> compareWithApiAll(@RequestBody CarFfnlgTrgtIncmpVO searchParams) {
|
||||
log.info("전체 API 호출 및 비교 요청 (미필) - 검색 조건: {}", searchParams);
|
||||
|
||||
try {
|
||||
// 페이징 비활성화
|
||||
searchParams.setPagingYn("N");
|
||||
|
||||
// 전체 목록 조회
|
||||
List<CarFfnlgTrgtIncmpVO> allData = service.selectList(searchParams);
|
||||
|
||||
// 목록을 Map 형태로 변환
|
||||
List<Map<String, String>> targetList = allData.stream()
|
||||
.map(vo -> {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("carFfnlgTrgtIncmpId", vo.getCarFfnlgTrgtIncmpId());
|
||||
map.put("vhclno", vo.getVhclno());
|
||||
map.put("inspVldPrd", vo.getInspVldPrd());
|
||||
map.put("ownrNm", vo.getOwnrNm());
|
||||
map.put("carNm", vo.getCarNm());
|
||||
return map;
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// API 호출 및 비교
|
||||
Map<String, Object> resultData = service.compareWithApi(targetList);
|
||||
|
||||
int successCount = (int) resultData.get("successCount");
|
||||
int failCount = (int) resultData.get("failCount");
|
||||
String message = String.format("전체 API 호출 및 비교 완료\n대상: %d건, 성공: %d건, 실패: %d건",
|
||||
allData.size(), successCount, failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("전체 API 호출 및 비교 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 일괄 삭제
|
||||
* @param deleteIds 삭제할 미필 과태료 대상 ID 목록
|
||||
* @return 삭제 결과
|
||||
*/
|
||||
@PostMapping("/deleteBatch.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "미필 과태료 대상 일괄 삭제", description = "선택된 미필 과태료 대상 목록을 일괄 삭제합니다.")
|
||||
public ResponseEntity<?> deleteBatch(@RequestBody List<String> deleteIds) {
|
||||
log.info("일괄 삭제 요청 - 선택된 데이터 건수: {}", deleteIds != null ? deleteIds.size() : 0);
|
||||
|
||||
try {
|
||||
if (deleteIds == null || deleteIds.isEmpty()) {
|
||||
return ApiResponseUtil.error("삭제할 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (String id : deleteIds) {
|
||||
try {
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(id);
|
||||
int result = service.delete(vo);
|
||||
|
||||
if (result > 0) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("삭제 실패 - ID: {}", id, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String message = String.format("삭제 완료\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
|
||||
Map<String, Object> resultData = new HashMap<>();
|
||||
resultData.put("successCount", successCount);
|
||||
resultData.put("failCount", failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("일괄 삭제 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 정보를 일괄 저장하는 AJAX 메소드
|
||||
* 생성, 수정, 삭제된 데이터를 처리합니다.
|
||||
*
|
||||
* @param modifyData 생성/수정/삭제할 데이터를 담은 VO 객체
|
||||
* @return 저장 결과 메시지와 성공/실패 상태를 담은 ResponseEntity 객체
|
||||
*/
|
||||
@PostMapping("/saveAll.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "미필 과태료 대상 정보 일괄 저장", description = "생성, 수정, 삭제된 미필 과태료 대상 데이터를 일괄 처리합니다.")
|
||||
public ResponseEntity<?> saveAllAjax(@RequestBody CarFfnlgTrgtIncmpModifiedDataVO modifyData) {
|
||||
log.info("미필 과태료 대상 일괄 저장 요청 - 수정: {}건, 생성: {}건, 삭제: {}건",
|
||||
modifyData.getUpdatedRows() != null ? modifyData.getUpdatedRows().size() : 0,
|
||||
modifyData.getCreatedRows() != null ? modifyData.getCreatedRows().size() : 0,
|
||||
modifyData.getDeletedRows() != null ? modifyData.getDeletedRows().size() : 0);
|
||||
|
||||
try {
|
||||
int result = service.saveCarFfnlgTrgtIncmps(modifyData);
|
||||
if (result > 0) {
|
||||
return ApiResponseUtil.success("미필 과태료 대상 정보가 저장되었습니다.");
|
||||
} else {
|
||||
return ApiResponseUtil.error("저장할 데이터가 없습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 일괄 저장 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("저장 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 인코딩된 MultipartFile을 EUC-KR 인코딩으로 변환
|
||||
*
|
||||
* @param utf8File UTF-8 인코딩된 원본 파일
|
||||
* @return EUC-KR 인코딩으로 변환된 파일
|
||||
* @throws IOException 파일 읽기/변환 중 오류 발생 시
|
||||
*/
|
||||
private MultipartFile convertUtf8ToEucKr(MultipartFile utf8File) throws IOException {
|
||||
// UTF-8로 파일 내용 읽기
|
||||
String content = new String(utf8File.getBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
// EUC-KR 바이트로 변환
|
||||
byte[] eucKrBytes = content.getBytes("EUC-KR");
|
||||
String eucKrContent = new String(eucKrBytes, "EUC-KR");
|
||||
|
||||
log.info("파일 인코딩 변환 - UTF-8({} bytes) → EUC-KR({} bytes)",
|
||||
utf8File.getSize(), eucKrBytes.length);
|
||||
|
||||
// EUC-KR 바이트로 변환된 새로운 MultipartFile 반환
|
||||
return new EucKrMultipartFile(
|
||||
utf8File.getName(),
|
||||
utf8File.getOriginalFilename(),
|
||||
utf8File.getContentType(),
|
||||
eucKrBytes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* EUC-KR 인코딩 변환을 위한 커스텀 MultipartFile 구현체
|
||||
*/
|
||||
private static class EucKrMultipartFile implements MultipartFile {
|
||||
private final String name;
|
||||
private final String originalFilename;
|
||||
private final String contentType;
|
||||
private final byte[] bytes;
|
||||
|
||||
public EucKrMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
|
||||
this.name = name;
|
||||
this.originalFilename = originalFilename;
|
||||
this.contentType = contentType;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return originalFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return bytes == null || bytes.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
|
||||
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(dest)) {
|
||||
fos.write(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 엑셀 다운로드
|
||||
*
|
||||
* @param paramVO 검색 조건을 담은 VO 객체
|
||||
* @param request HTTP 요청 객체
|
||||
* @param response HTTP 응답 객체
|
||||
*/
|
||||
@PostMapping("/excel.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 엑셀 다운로드", description = "미필 과태료 대상 목록을 엑셀 파일로 다운로드합니다.")
|
||||
public void downloadExcel(
|
||||
@ModelAttribute CarFfnlgTrgtIncmpVO paramVO,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
log.debug("미필 과태료 대상 목록 엑셀 다운로드 요청");
|
||||
|
||||
// 페이징 처리 없이 전체 데이터 조회
|
||||
paramVO.setPagingYn("N");
|
||||
|
||||
// 미필 과태료 대상 목록 조회
|
||||
List<CarFfnlgTrgtIncmpExcelVO> excelList = service.selectListForExcel(paramVO);
|
||||
|
||||
// 엑셀 파일 생성 및 다운로드
|
||||
String filename = "미필_과태료대상목록_" + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
|
||||
new SxssfExcelFile(ExcelSheetData.of(excelList, CarFfnlgTrgtIncmpExcelVO.class, "미필 과태료 대상 목록 " + excelList.size() + "건"), request, response, filename);
|
||||
|
||||
log.debug("미필 과태료 대상 목록 엑셀 다운로드 완료 - 파일명: {}, 건수: {}", filename, excelList.size());
|
||||
} catch (Exception e) {
|
||||
log.error("엑셀 다운로드 중 오류 발생", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,572 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.controller;
|
||||
|
||||
import egovframework.constant.MessageConstants;
|
||||
import egovframework.constant.TilesConstants;
|
||||
import egovframework.util.ApiResponseUtil;
|
||||
import egovframework.util.SessionUtil;
|
||||
import egovframework.util.excel.ExcelSheetData;
|
||||
import egovframework.util.excel.SxssfExcelFile;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpExcelVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpModifiedDataVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.CarFfnlgTrgtIncmpService;
|
||||
import go.kr.project.common.model.CmmnCodeSearchVO;
|
||||
import go.kr.project.common.service.CommonCodeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 자동차 미필 과태료 대상 등록 Controller
|
||||
* 미필 과태료 대상 목록 조회, PRN 파일 업로드 기능 제공
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/carInspectionPenalty/registration-om")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Tag(name = "자동차 미필 과태료 대상 등록", description = "자동차 미필 과태료 대상 등록 및 목록 조회 API")
|
||||
public class CarFfnlgTrgtIncmpController {
|
||||
|
||||
private final CarFfnlgTrgtIncmpService service;
|
||||
private final CommonCodeService commonCodeService;
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 화면
|
||||
* @param model 모델
|
||||
* @return 목록 화면 경로
|
||||
*/
|
||||
@GetMapping("/list.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 화면", description = "미필 과태료 대상 목록 조회 화면을 제공합니다.")
|
||||
public String list(Model model) {
|
||||
log.debug("미필 과태료 대상 목록 화면 요청");
|
||||
|
||||
// 업무 처리 상태 코드 조회 (공통코드)
|
||||
CmmnCodeSearchVO taskPrcsSttsCdSearchVO = CmmnCodeSearchVO.builder()
|
||||
.searchCdGroupId("TASK_PRCS_STTS_CD")
|
||||
.searchUseYn("Y")
|
||||
.sortColumn("SORT_ORDR")
|
||||
.sortAscending(true)
|
||||
.build();
|
||||
model.addAttribute("taskPrcsSttsCdList", commonCodeService.selectCodeDetailList(taskPrcsSttsCdSearchVO));
|
||||
|
||||
// 미필 과태료 대상 구분 코드 조회 (공통코드)
|
||||
CmmnCodeSearchVO ffnlgTrgtSeCdSearchVO = CmmnCodeSearchVO.builder()
|
||||
.searchCdGroupId("FFNLG_TRGT_SE_CD")
|
||||
.searchUseYn("Y")
|
||||
.sortColumn("SORT_ORDR")
|
||||
.sortAscending(true)
|
||||
.build();
|
||||
model.addAttribute("ffnlgTrgtSeCdList", commonCodeService.selectCodeDetailList(ffnlgTrgtSeCdSearchVO));
|
||||
|
||||
return "carInspectionPenalty/registrationOm/list" + TilesConstants.BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 조회 AJAX
|
||||
* @param paramVO 검색 조건
|
||||
* @return 목록 데이터
|
||||
*/
|
||||
@PostMapping("/list.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 목록 조회", description = "미필 과태료 대상 목록을 조회하고 JSON 형식으로 반환합니다.")
|
||||
public ResponseEntity<?> listAjax(@ModelAttribute CarFfnlgTrgtIncmpVO paramVO) {
|
||||
log.debug("미필 과태료 대상 목록 조회 AJAX - 검색조건: {}", paramVO);
|
||||
|
||||
// 1. 총 개수 조회
|
||||
int totalCount = service.selectListTotalCount(paramVO);
|
||||
|
||||
// 2. totalCount 설정
|
||||
paramVO.setTotalCount(totalCount);
|
||||
|
||||
// 3. 페이징 활성화
|
||||
paramVO.setPagingYn("Y");
|
||||
|
||||
// 목록 조회
|
||||
List<CarFfnlgTrgtIncmpVO> list = service.selectList(paramVO);
|
||||
|
||||
return ApiResponseUtil.successWithGrid(list, paramVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 다운로드 (EUC-KR 텍스트)
|
||||
* 샘플 파일과 동일한 고정폭 포맷으로 생성하여 다운로드 제공합니다.
|
||||
* - 인코딩: EUC-KR (한글 2바이트)
|
||||
*/
|
||||
@GetMapping("/download.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 다운로드", description = "EUC-KR 인코딩의 고정폭 텍스트로 목록을 샘플과 동일한 포맷으로 다운로드합니다.")
|
||||
public void download(
|
||||
@ModelAttribute CarFfnlgTrgtIncmpVO paramVO,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
try {
|
||||
|
||||
// 페이징 없이 전체 조회를 위해 페이징 비활성화
|
||||
paramVO.setPagingYn("N");
|
||||
|
||||
// 서비스에서 EUC-KR 텍스트 콘텐츠 생성
|
||||
byte[] fileBytes = service.generateEucKrDownloadBytes(paramVO);
|
||||
|
||||
// EUC-KR 바이트를 UTF-8 바이트로 변환 (다운로드 시에만)
|
||||
String content = new String(fileBytes, "EUC-KR");
|
||||
byte[] utfFileBytes = content.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
String fileName = URLEncoder.encode("미필_유효기간경과_과태료부과대상_리스트.prn", "UTF-8");
|
||||
|
||||
// 응답 헤더 설정 (텍스트 파일, UTF-8 인코딩)
|
||||
response.setContentType("text/plain; charset=UTF-8");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||
response.setContentLength(utfFileBytes.length);
|
||||
|
||||
// UTF-8 바이트 스트림으로 전송
|
||||
response.getOutputStream().write(utfFileBytes);
|
||||
response.getOutputStream().flush();
|
||||
} catch (Exception e) {
|
||||
log.error("목록 다운로드 중 오류", e);
|
||||
try {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
response.getWriter().write("다운로드 처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 업로드 팝업 화면
|
||||
* @return 팝업 화면
|
||||
*/
|
||||
@GetMapping("/uploadPopup.do")
|
||||
@Operation(summary = "파일 업로드 팝업", description = "PRN 파일 업로드 팝업 화면을 제공합니다.")
|
||||
public ModelAndView uploadPopup() {
|
||||
log.debug("파일 업로드 팝업 화면 요청");
|
||||
|
||||
ModelAndView mav = new ModelAndView("carInspectionPenalty/registrationOm/uploadPopup" + TilesConstants.POPUP);
|
||||
|
||||
return mav;
|
||||
}
|
||||
|
||||
/**
|
||||
* PRN 파일 업로드 및 처리
|
||||
*
|
||||
* 주의: 파일 단위로 업로드하므로 한 건이라도 실패하면 전체 롤백 처리됨
|
||||
*
|
||||
* @param file 업로드된 PRN 파일
|
||||
* @return 처리 결과
|
||||
*/
|
||||
@PostMapping("/upload.ajax")
|
||||
@Operation(summary = "PRN 파일 업로드", description = "PRN 파일을 업로드하고 파싱하여 DB에 저장합니다. 한 건이라도 실패 시 전체 롤백됩니다.")
|
||||
public ResponseEntity<?> upload(
|
||||
@Parameter(description = "PRN 파일") @RequestParam("file") MultipartFile file) {
|
||||
|
||||
log.info("PRN 파일 업로드 요청 - 파일명: {}", file != null ? file.getOriginalFilename() : "null");
|
||||
|
||||
try {
|
||||
// 세션에서 사용자 ID 가져오기
|
||||
String rgtr = SessionUtil.getUserId();
|
||||
if (rgtr == null || rgtr.isEmpty()) {
|
||||
return ApiResponseUtil.error("로그인 정보가 없습니다.");
|
||||
}
|
||||
|
||||
// UTF-8 파일을 EUC-KR로 변환 (시스템은 EUC-KR 기준으로 처리)
|
||||
MultipartFile convertedFile = convertUtf8ToEucKr(file);
|
||||
|
||||
// 파일 업로드 및 파싱 (한 건이라도 실패 시 전체 롤백)
|
||||
Map<String, Object> result = service.uploadAndParsePrnFile(convertedFile, rgtr);
|
||||
|
||||
int successCount = (int) result.get("successCount");
|
||||
int failCount = (int) result.get("failCount");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> errorMessages = (List<String>) result.get("errorMessages");
|
||||
|
||||
if (failCount == 0 && successCount > 0) {
|
||||
// 모든 데이터가 성공적으로 저장됨
|
||||
String message = String.format("파일 업로드가 완료되었습니다.\n\n성공: %d건", successCount);
|
||||
return ApiResponseUtil.success(result, message);
|
||||
} else if (successCount > 0) {
|
||||
// 일부 성공, 일부 실패
|
||||
String message = String.format("파일 업로드가 완료되었습니다.\n\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
return ApiResponseUtil.success(result, message);
|
||||
} else {
|
||||
// 모두 실패
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (!errorMessages.isEmpty()) {
|
||||
for (String errorMsg : errorMessages) {
|
||||
message.append(errorMsg).append("\n");
|
||||
}
|
||||
} else {
|
||||
message.append("파일 업로드 중 오류가 발생했습니다.");
|
||||
}
|
||||
return ApiResponseUtil.error(message.toString());
|
||||
}
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// 데이터 처리 중 오류 발생 - 전체 롤백됨
|
||||
log.error("PRN 파일 업로드 중 오류 발생 - 전체 롤백", e);
|
||||
return ApiResponseUtil.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
// 예상치 못한 오류
|
||||
log.error("PRN 파일 업로드 중 예상치 못한 오류 발생", e);
|
||||
return ApiResponseUtil.error("파일 업로드 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 상세 조회
|
||||
* @param carFfnlgTrgtIncmpId 미필 과태료 대상 ID
|
||||
* @return 상세 정보
|
||||
*/
|
||||
@GetMapping("/selectOne.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 상세 조회", description = "미필 과태료 대상 상세 정보를 조회합니다.")
|
||||
public ResponseEntity<?> selectOne(
|
||||
@Parameter(description = "미필 과태료 대상 ID") @RequestParam String carFfnlgTrgtIncmpId) {
|
||||
|
||||
log.debug("미필 과태료 대상 상세 조회 - ID: {}", carFfnlgTrgtIncmpId);
|
||||
|
||||
try {
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
|
||||
|
||||
CarFfnlgTrgtIncmpVO result = service.selectOne(vo);
|
||||
|
||||
if (result != null) {
|
||||
return ApiResponseUtil.success(result, "조회 성공");
|
||||
} else {
|
||||
return ApiResponseUtil.error("조회된 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 상세 조회 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("조회 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 삭제 (논리삭제)
|
||||
* @param carFfnlgTrgtIncmpId 미필 과태료 대상 ID
|
||||
* @return 삭제 결과
|
||||
*/
|
||||
@PostMapping("/delete.ajax")
|
||||
@Operation(summary = "미필 과태료 대상 삭제", description = "미필 과태료 대상을 삭제(논리삭제)합니다.")
|
||||
public ResponseEntity<?> delete(
|
||||
@Parameter(description = "미필 과태료 대상 ID") @RequestParam String carFfnlgTrgtIncmpId) {
|
||||
|
||||
log.info("미필 과태료 대상 삭제 요청 - ID: {}", carFfnlgTrgtIncmpId);
|
||||
|
||||
try {
|
||||
String dltr = SessionUtil.getUserId();
|
||||
if (dltr == null || dltr.isEmpty()) {
|
||||
return ApiResponseUtil.error("로그인 정보가 없습니다.");
|
||||
}
|
||||
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
|
||||
vo.setDltr(dltr);
|
||||
|
||||
int result = service.delete(vo);
|
||||
|
||||
if (result > 0) {
|
||||
return ApiResponseUtil.success(MessageConstants.Common.DELETE_SUCCESS);
|
||||
} else {
|
||||
return ApiResponseUtil.error(MessageConstants.Common.DELETE_ERROR);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 삭제 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택된 목록에 대해 API 호출 및 기본정보/등록원부 비교
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
* @param targetList 선택된 미필 과태료 대상 목록
|
||||
* @return 비교 결과
|
||||
*/
|
||||
@PostMapping("/compareWithApi.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "API 호출 및 데이터 비교", description = "선택된 목록에 대해 차량 API를 호출하고 기본정보 및 등록원부와 비교합니다. 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일")
|
||||
public ResponseEntity<?> compareWithApi(@RequestBody List<Map<String, String>> targetList) {
|
||||
log.info("API 호출 및 비교 요청 (미필) - 선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);
|
||||
|
||||
try {
|
||||
Map<String, Object> resultData = service.compareWithApi(targetList);
|
||||
|
||||
int successCount = (int) resultData.get("successCount");
|
||||
int failCount = (int) resultData.get("failCount");
|
||||
String message = String.format("API 호출 및 비교 완료\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("파라미터 검증 오류", e);
|
||||
return ApiResponseUtil.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("API 호출 및 비교 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검색조건 전체 목록에 대해 API 호출 및 기본정보/등록원부 비교 (페이징 없이)
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
* @param searchParams 검색 조건 파라미터
|
||||
* @return 비교 결과
|
||||
*/
|
||||
@PostMapping("/compareWithApiAll.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "검색조건 전체 API 호출 및 데이터 비교", description = "검색조건에 해당하는 전체 목록에 대해 차량 API를 호출하고 비교합니다. 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일")
|
||||
public ResponseEntity<?> compareWithApiAll(@RequestBody CarFfnlgTrgtIncmpVO searchParams) {
|
||||
log.info("전체 API 호출 및 비교 요청 (미필) - 검색 조건: {}", searchParams);
|
||||
|
||||
try {
|
||||
// 페이징 비활성화
|
||||
searchParams.setPagingYn("N");
|
||||
|
||||
// 전체 목록 조회
|
||||
List<CarFfnlgTrgtIncmpVO> allData = service.selectList(searchParams);
|
||||
|
||||
// 목록을 Map 형태로 변환
|
||||
List<Map<String, String>> targetList = allData.stream()
|
||||
.map(vo -> {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("carFfnlgTrgtIncmpId", vo.getCarFfnlgTrgtIncmpId());
|
||||
map.put("vhclno", vo.getVhclno());
|
||||
map.put("inspVldPrd", vo.getInspVldPrd());
|
||||
map.put("ownrNm", vo.getOwnrNm());
|
||||
map.put("carNm", vo.getCarNm());
|
||||
return map;
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// API 호출 및 비교
|
||||
Map<String, Object> resultData = service.compareWithApi(targetList);
|
||||
|
||||
int successCount = (int) resultData.get("successCount");
|
||||
int failCount = (int) resultData.get("failCount");
|
||||
String message = String.format("전체 API 호출 및 비교 완료\n대상: %d건, 성공: %d건, 실패: %d건",
|
||||
allData.size(), successCount, failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("전체 API 호출 및 비교 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("처리 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 일괄 삭제
|
||||
* @param deleteIds 삭제할 미필 과태료 대상 ID 목록
|
||||
* @return 삭제 결과
|
||||
*/
|
||||
@PostMapping("/deleteBatch.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "미필 과태료 대상 일괄 삭제", description = "선택된 미필 과태료 대상 목록을 일괄 삭제합니다.")
|
||||
public ResponseEntity<?> deleteBatch(@RequestBody List<String> deleteIds) {
|
||||
log.info("일괄 삭제 요청 - 선택된 데이터 건수: {}", deleteIds != null ? deleteIds.size() : 0);
|
||||
|
||||
try {
|
||||
if (deleteIds == null || deleteIds.isEmpty()) {
|
||||
return ApiResponseUtil.error("삭제할 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (String id : deleteIds) {
|
||||
try {
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
vo.setCarFfnlgTrgtIncmpId(id);
|
||||
int result = service.delete(vo);
|
||||
|
||||
if (result > 0) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("삭제 실패 - ID: {}", id, e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String message = String.format("삭제 완료\n성공: %d건, 실패: %d건", successCount, failCount);
|
||||
|
||||
Map<String, Object> resultData = new HashMap<>();
|
||||
resultData.put("successCount", successCount);
|
||||
resultData.put("failCount", failCount);
|
||||
|
||||
return ApiResponseUtil.success(resultData, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("일괄 삭제 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("삭제 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 정보를 일괄 저장하는 AJAX 메소드
|
||||
* 생성, 수정, 삭제된 데이터를 처리합니다.
|
||||
*
|
||||
* @param modifyData 생성/수정/삭제할 데이터를 담은 VO 객체
|
||||
* @return 저장 결과 메시지와 성공/실패 상태를 담은 ResponseEntity 객체
|
||||
*/
|
||||
@PostMapping("/saveAll.ajax")
|
||||
@ResponseBody
|
||||
@Operation(summary = "미필 과태료 대상 정보 일괄 저장", description = "생성, 수정, 삭제된 미필 과태료 대상 데이터를 일괄 처리합니다.")
|
||||
public ResponseEntity<?> saveAllAjax(@RequestBody CarFfnlgTrgtIncmpModifiedDataVO modifyData) {
|
||||
log.info("미필 과태료 대상 일괄 저장 요청 - 수정: {}건, 생성: {}건, 삭제: {}건",
|
||||
modifyData.getUpdatedRows() != null ? modifyData.getUpdatedRows().size() : 0,
|
||||
modifyData.getCreatedRows() != null ? modifyData.getCreatedRows().size() : 0,
|
||||
modifyData.getDeletedRows() != null ? modifyData.getDeletedRows().size() : 0);
|
||||
|
||||
try {
|
||||
int result = service.saveCarFfnlgTrgtIncmps(modifyData);
|
||||
if (result > 0) {
|
||||
return ApiResponseUtil.success("미필 과태료 대상 정보가 저장되었습니다.");
|
||||
} else {
|
||||
return ApiResponseUtil.error("저장할 데이터가 없습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("미필 과태료 대상 일괄 저장 중 오류 발생", e);
|
||||
return ApiResponseUtil.error("저장 중 오류가 발생했습니다: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 인코딩된 MultipartFile을 EUC-KR 인코딩으로 변환
|
||||
*
|
||||
* @param utf8File UTF-8 인코딩된 원본 파일
|
||||
* @return EUC-KR 인코딩으로 변환된 파일
|
||||
* @throws IOException 파일 읽기/변환 중 오류 발생 시
|
||||
*/
|
||||
private MultipartFile convertUtf8ToEucKr(MultipartFile utf8File) throws IOException {
|
||||
// UTF-8로 파일 내용 읽기
|
||||
String content = new String(utf8File.getBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
// EUC-KR 바이트로 변환
|
||||
byte[] eucKrBytes = content.getBytes("EUC-KR");
|
||||
String eucKrContent = new String(eucKrBytes, "EUC-KR");
|
||||
|
||||
log.info("파일 인코딩 변환 - UTF-8({} bytes) → EUC-KR({} bytes)",
|
||||
utf8File.getSize(), eucKrBytes.length);
|
||||
|
||||
// EUC-KR 바이트로 변환된 새로운 MultipartFile 반환
|
||||
return new EucKrMultipartFile(
|
||||
utf8File.getName(),
|
||||
utf8File.getOriginalFilename(),
|
||||
utf8File.getContentType(),
|
||||
eucKrBytes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* EUC-KR 인코딩 변환을 위한 커스텀 MultipartFile 구현체
|
||||
*/
|
||||
private static class EucKrMultipartFile implements MultipartFile {
|
||||
private final String name;
|
||||
private final String originalFilename;
|
||||
private final String contentType;
|
||||
private final byte[] bytes;
|
||||
|
||||
public EucKrMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
|
||||
this.name = name;
|
||||
this.originalFilename = originalFilename;
|
||||
this.contentType = contentType;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return originalFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return bytes == null || bytes.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
|
||||
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(dest)) {
|
||||
fos.write(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 엑셀 다운로드
|
||||
*
|
||||
* @param paramVO 검색 조건을 담은 VO 객체
|
||||
* @param request HTTP 요청 객체
|
||||
* @param response HTTP 응답 객체
|
||||
*/
|
||||
@PostMapping("/excel.do")
|
||||
@Operation(summary = "미필 과태료 대상 목록 엑셀 다운로드", description = "미필 과태료 대상 목록을 엑셀 파일로 다운로드합니다.")
|
||||
public void downloadExcel(
|
||||
@ModelAttribute CarFfnlgTrgtIncmpVO paramVO,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
log.debug("미필 과태료 대상 목록 엑셀 다운로드 요청");
|
||||
|
||||
// 페이징 처리 없이 전체 데이터 조회
|
||||
paramVO.setPagingYn("N");
|
||||
|
||||
// 미필 과태료 대상 목록 조회
|
||||
List<CarFfnlgTrgtIncmpExcelVO> excelList = service.selectListForExcel(paramVO);
|
||||
|
||||
// 엑셀 파일 생성 및 다운로드
|
||||
String filename = "미필_과태료대상목록_" + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
|
||||
new SxssfExcelFile(ExcelSheetData.of(excelList, CarFfnlgTrgtIncmpExcelVO.class, "미필 과태료 대상 목록 " + excelList.size() + "건"), request, response, filename);
|
||||
|
||||
log.debug("미필 과태료 대상 목록 엑셀 다운로드 완료 - 파일명: {}, 건수: {}", filename, excelList.size());
|
||||
} catch (Exception e) {
|
||||
log.error("엑셀 다운로드 중 오류 발생", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.mapper;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpExcelVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 자동차 과태료 대상 미필 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface CarFfnlgTrgtIncmpMapper {
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 총 개수 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 총 개수
|
||||
*/
|
||||
int selectListTotalCount(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 목록
|
||||
*/
|
||||
List<CarFfnlgTrgtIncmpVO> selectList(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 상세 조회
|
||||
* @param vo 조회 조건 (carFfnlgTrgtIncmpId)
|
||||
* @return 상세 정보
|
||||
*/
|
||||
CarFfnlgTrgtIncmpVO selectOne(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 등록
|
||||
* @param vo 등록할 데이터
|
||||
* @return 등록 건수
|
||||
*/
|
||||
int insert(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 수정
|
||||
* @param vo 수정할 데이터
|
||||
* @return 수정 건수
|
||||
*/
|
||||
int update(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필의 처리상태와 비고만 수정
|
||||
* @param vo 수정할 데이터 (carFfnlgTrgtIncmpId, taskPrcsSttsCd, rmrk)
|
||||
* @return 수정 건수
|
||||
*/
|
||||
int updateTaskPrcsSttsCdAndRmrk(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 삭제 (논리삭제)
|
||||
* @param vo 삭제할 데이터 (carFfnlgTrgtIncmpId, dltr)
|
||||
* @return 삭제 건수
|
||||
*/
|
||||
int delete(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 차량번호와 검사유효기간으로 중복 체크 (삭제되지 않은 데이터 중)
|
||||
* @param vo 조회 조건 (vhclno, inspVldPrd)
|
||||
* @return 존재 개수
|
||||
*/
|
||||
int checkDuplicateVhclno(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 시군구 코드로 시군구명 조회
|
||||
* @param sggCd 시군구 코드 (5자리)
|
||||
* @return 시군구명
|
||||
*/
|
||||
String selectSggNmBySggCd(String sggCd);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 엑셀 다운로드용 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 엑셀 다운로드용 목록
|
||||
*/
|
||||
List<CarFfnlgTrgtIncmpExcelVO> selectListForExcel(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 미필 부과일자 가산일 조회 (OM_DAY_CD 코드의 D값)
|
||||
* @return 가산일 (예: 145)
|
||||
*/
|
||||
String selectOmDayPlusDay();
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.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 CarFfnlgTrgtIncmpExcelVO {
|
||||
|
||||
/** 접수일자 */
|
||||
@ExcelColumn(headerName = "접수일자", headerWidth = 15, align = ExcelColumn.Align.CENTER)
|
||||
private String rcptYmd;
|
||||
|
||||
/** 프로그램ID */
|
||||
@ExcelColumn(headerName = "프로그램ID", headerWidth = 12, align = ExcelColumn.Align.CENTER)
|
||||
private String prgrmId;
|
||||
|
||||
/** 처리일자 */
|
||||
@ExcelColumn(headerName = "처리일자", headerWidth = 30, align = ExcelColumn.Align.CENTER)
|
||||
private String prcsYmd;
|
||||
|
||||
/** 번호 */
|
||||
@ExcelColumn(headerName = "번호", headerWidth = 8, align = ExcelColumn.Align.CENTER)
|
||||
private Integer no;
|
||||
|
||||
/** 차량번호 */
|
||||
@ExcelColumn(headerName = "차량번호", headerWidth = 15, align = ExcelColumn.Align.CENTER)
|
||||
private String vhclno;
|
||||
|
||||
/** 소유자명 */
|
||||
@ExcelColumn(headerName = "소유자명", headerWidth = 20, 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 = 50, align = ExcelColumn.Align.LEFT)
|
||||
private String useStrhldAddr;
|
||||
|
||||
/** 검사유효기간 */
|
||||
@ExcelColumn(headerName = "검사유효기간", headerWidth = 25, align = ExcelColumn.Align.CENTER)
|
||||
private String inspVldPrd;
|
||||
|
||||
/** 처리상태 */
|
||||
@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;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.model;
|
||||
|
||||
import go.kr.project.common.model.PagingVO;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 수정 데이터를 담는 VO 클래스
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class CarFfnlgTrgtIncmpModifiedDataVO extends PagingVO {
|
||||
|
||||
private List<CarFfnlgTrgtIncmpVO> createdRows;
|
||||
private List<CarFfnlgTrgtIncmpVO> updatedRows;
|
||||
private List<CarFfnlgTrgtIncmpVO> deletedRows;
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import go.kr.project.common.model.PagingVO;
|
||||
import lombok.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 자동차 과태료 대상 미필 VO
|
||||
* 테이블: tb_car_ffnlg_trgt_incmp
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CarFfnlgTrgtIncmpVO extends PagingVO {
|
||||
|
||||
// 기본키
|
||||
private String carFfnlgTrgtIncmpId; // 자동차 과태료 대상 미필 ID
|
||||
|
||||
// 업무 필드 (헤더에서 파싱)
|
||||
private String rcptYmd; // 접수 일자
|
||||
private String prgrmId; // 프로그램 ID
|
||||
private String prcsYmd; // 처리 일자
|
||||
private String otptDt; // 출력 일시
|
||||
|
||||
// 업무 필드 (데이터 행에서 파싱)
|
||||
private Integer no; // 번호
|
||||
private String vhclno; // 차량번호
|
||||
private String ownrNm; // 소유자 명
|
||||
private String rrno; // 주민등록번호
|
||||
private String carNm; // 자동차 명
|
||||
private String useStrhldAddr; // 사용 본거지 주소
|
||||
private String inspVldPrd; // 검사 유효 기간
|
||||
|
||||
// 업무 처리 필드
|
||||
private String taskPrcsSttsCd; // 업무 처리 상태 코드 (01=접수, 02=처리중, 03=완료)
|
||||
private String taskPrcsYmd; // 업무 처리 일자
|
||||
private String rmrk; // 비고
|
||||
|
||||
// API 연동 필드
|
||||
private String carBassMatterInqireId; // 자동차 기본 사항 조회 ID
|
||||
private String carLedgerFrmbkId; // 자동차 등록 원부 갑 ID
|
||||
private String carBscMttrInqFlnm; // 자동차 기본 사항 조회 성명 (상품용일 때 저장)
|
||||
private String carBscMttrInqSggCd; // 자동차 기본 사항 조회 시군구 코드 (이첩일 때 저장)
|
||||
private String carBscMttrInqSggNm; // 자동차 기본 사항 조회 시군구 명 (이첩일 때 저장)
|
||||
private String carRegFrmbkChgTaskSeCd; // 자동차 등록 원부갑 변경 업무 구분 코드
|
||||
private String carRegFrmbkChgTaskSeNm; // 자동차 등록 원부갑 변경 업무 구분 명
|
||||
private String carRegFrmbkChgYmd; // 자동차 등록 원부갑 변경 일자
|
||||
private String carRegFrmbkDtl; // 자동차 등록 원부갑 상세
|
||||
|
||||
// 감사 필드
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
|
||||
private LocalDateTime regDt; // 등록 일시
|
||||
private String rgtr; // 등록자
|
||||
private String delYn; // 삭제 여부
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
|
||||
private LocalDateTime delDt; // 삭제 일시
|
||||
private String dltr; // 삭제자
|
||||
|
||||
// 조회용 필드
|
||||
private String taskPrcsSttsCdNm; // 업무 처리 상태 코드명
|
||||
private String rgtrNm; // 등록자명
|
||||
|
||||
// 검색 조건 필드
|
||||
private String schRcptYmdStart; // 검색 시작 접수 일자
|
||||
private String schRcptYmdEnd; // 검색 종료 접수 일자
|
||||
private String schVhclno; // 검색 차량번호
|
||||
private String schOwnrNm; // 검색 소유자명
|
||||
private List<String> schTaskPrcsSttsCd; // 검색 업무 처리 상태 코드 (다중 선택 가능)
|
||||
private String schPrcsYmdStart; // 검색 시작 처리 일자
|
||||
private String schPrcsYmdEnd; // 검색 종료 처리 일자
|
||||
|
||||
// 부과일자 계산용 필드 (검사유효기간 종료일 + 145일)
|
||||
private String levyCrtrYmd; // 부과기준일자 (API 호출 시 사용)
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpExcelVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpModifiedDataVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 자동차 과태료 대상 미필 Service
|
||||
*/
|
||||
public interface CarFfnlgTrgtIncmpService {
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 총 개수 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 총 개수
|
||||
*/
|
||||
int selectListTotalCount(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 목록
|
||||
*/
|
||||
List<CarFfnlgTrgtIncmpVO> selectList(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 상세 조회
|
||||
* @param vo 조회 조건 (carFfnlgTrgtIncmpId)
|
||||
* @return 상세 정보
|
||||
*/
|
||||
CarFfnlgTrgtIncmpVO selectOne(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 등록
|
||||
* @param vo 등록할 데이터
|
||||
* @return 등록 건수
|
||||
*/
|
||||
int insert(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 수정
|
||||
* @param vo 수정할 데이터
|
||||
* @return 수정 건수
|
||||
*/
|
||||
int update(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 삭제 (논리삭제)
|
||||
* @param vo 삭제할 데이터 (carFfnlgTrgtIncmpId, dltr)
|
||||
* @return 삭제 건수
|
||||
*/
|
||||
int delete(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* PRN 파일 업로드 및 파싱하여 DB 저장
|
||||
* @param file 업로드된 PRN 파일
|
||||
* @param rgtr 등록자 ID
|
||||
* @return 처리 결과 (성공 건수, 실패 건수, 오류 메시지 목록)
|
||||
*/
|
||||
Map<String, Object> uploadAndParsePrnFile(MultipartFile file, String rgtr);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록을 EUC-KR 인코딩의 고정폭 텍스트 바이트로 생성
|
||||
* 샘플 텍스트와 동일한 포맷으로 출력합니다.
|
||||
*
|
||||
* @param vo 검색 조건
|
||||
* @return EUC-KR 인코딩 바이트 배열
|
||||
*/
|
||||
byte[] generateEucKrDownloadBytes(CarFfnlgTrgtIncmpVO vo);
|
||||
|
||||
/**
|
||||
* 선택된 목록에 대해 API 호출 및 기본정보/등록원부 비교
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + 145일
|
||||
*
|
||||
* @param targetList 선택된 과태료 대상 미필 목록 (carFfnlgTrgtIncmpId, vhclno, inspVldPrd 포함)
|
||||
* @return 비교 결과 (compareResults, totalCount, successCount, failCount)
|
||||
*/
|
||||
Map<String, Object> compareWithApi(List<Map<String, String>> targetList);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 정보를 일괄 저장
|
||||
* 생성, 수정, 삭제된 데이터를 처리합니다.
|
||||
*
|
||||
* @param modifyData 생성/수정/삭제할 데이터를 담은 VO 객체
|
||||
* @return 저장 건수
|
||||
*/
|
||||
int saveCarFfnlgTrgtIncmps(CarFfnlgTrgtIncmpModifiedDataVO modifyData);
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 목록 엑셀 다운로드용 조회
|
||||
* @param vo 검색 조건
|
||||
* @return 엑셀 다운로드용 목록
|
||||
*/
|
||||
List<CarFfnlgTrgtIncmpExcelVO> selectListForExcel(CarFfnlgTrgtIncmpVO vo);
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 비교 서비스
|
||||
*
|
||||
* <p>차량 정보를 외부 API와 비교하여 상태를 자동으로 분류합니다.</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
public interface ComparisonOmService {
|
||||
|
||||
/**
|
||||
* 비교 로직을 실행하고 업무 처리 상태를 업데이트합니다.
|
||||
*
|
||||
* <p>실행 순서:</p>
|
||||
* <ol>
|
||||
* <li>상품용 체크</li>
|
||||
* <li>이첩 체크</li>
|
||||
* <li>향후 추가될 비교 로직들...</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>한 가지 조건이라도 만족하면 즉시 해당 상태로 업데이트하고 종료합니다.</p>
|
||||
* <p>미필의 경우 levyCrtrYmd(부과일자)가 미리 계산되어 전달됩니다.</p>
|
||||
*
|
||||
* @param existingData 기존 과태료 대상 미필 데이터 (levyCrtrYmd 포함)
|
||||
* @return 처리 상태 코드 (02=상품용, 03=이첩, null=해당없음)
|
||||
*/
|
||||
String executeComparison(CarFfnlgTrgtIncmpVO existingData);
|
||||
}
|
||||
@ -0,0 +1,743 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.SessionUtil;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.config.CarFfnlgPrnParseConfig;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpExcelVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpModifiedDataVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.CarFfnlgTrgtIncmpService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.ComparisonOmService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 자동차 과태료 대상 미필 Service 구현체
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implements CarFfnlgTrgtIncmpService {
|
||||
|
||||
private final CarFfnlgTrgtIncmpMapper mapper;
|
||||
private final CarFfnlgPrnParseConfig parseConfig;
|
||||
private final ComparisonOmService comparisonOmService;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
@Override
|
||||
public int selectListTotalCount(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.selectListTotalCount(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CarFfnlgTrgtIncmpVO> selectList(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.selectList(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarFfnlgTrgtIncmpVO selectOne(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.selectOne(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int insert(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.insert(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int update(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.update(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int delete(CarFfnlgTrgtIncmpVO vo) {
|
||||
return mapper.delete(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* PRN 파일 업로드 및 파싱하여 DB 저장
|
||||
*
|
||||
* 파일 형식: 고정폭 PRN 파일 (헤더 3줄 + 데이터 2줄씩)
|
||||
* - 헤더 1줄: 프로그램ID
|
||||
* - 헤더 2줄: 처리일자
|
||||
* - 헤더 3줄: 출력일시
|
||||
* - 데이터: 4번째 라인부터 시작, 2줄 1세트
|
||||
* 1) 첫 번째 줄: 번호/차량번호/소유자명/차명/사용본거지주소/검사유효기간
|
||||
* 2) 두 번째 줄: 주민등록번호/사용본거지주소(나머지)
|
||||
*
|
||||
* 주의: 파일 단위로 업로드하므로 한 건이라도 실패하면 전체 롤백 처리됨
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> uploadAndParsePrnFile(MultipartFile file, String rgtr) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
int successCount = 0;
|
||||
int dataLineNumber = 0;
|
||||
|
||||
try {
|
||||
// 파일 검증
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new IllegalArgumentException("파일이 선택되지 않았습니다.");
|
||||
}
|
||||
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename == null || (!originalFilename.toLowerCase().endsWith(".prn") && !originalFilename.toLowerCase().endsWith(".txt"))) {
|
||||
throw new IllegalArgumentException("PRN, TXT 파일만 업로드 가능합니다. 선택된 파일: " + originalFilename);
|
||||
}
|
||||
|
||||
if (file.getSize() > 50 * 1024 * 1024) {
|
||||
throw new IllegalArgumentException("파일 크기는 50MB를 초과할 수 없습니다. 파일 크기: " + (file.getSize() / 1024 / 1024) + "MB");
|
||||
}
|
||||
|
||||
log.info("PRN 파일 업로드 시작 - 파일명: {}, 크기: {} bytes", originalFilename, file.getSize());
|
||||
|
||||
String encoding = parseConfig.getEncoding();
|
||||
log.info("파일 인코딩: {}, 한글 바이트 크기: {}", encoding, parseConfig.getHangulByteSize());
|
||||
|
||||
List<String> allLines = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(file.getInputStream(), encoding))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
allLines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 파일 최소 라인 검증 (헤더 7라인 + 데이터 최소 2라인)
|
||||
if (allLines.size() < 9) {
|
||||
throw new IllegalArgumentException("파일 형식이 올바르지 않습니다. 최소 9라인 이상이어야 합니다. 현재 라인 수: " + allLines.size());
|
||||
}
|
||||
|
||||
log.info("파일 읽기 완료 - 총 라인 수: {}", allLines.size());
|
||||
|
||||
// 헤더 파싱 (3, 4번째 줄에서 추출)
|
||||
// Line 3: " 프로그램 ID : VGD01B"
|
||||
// Line 4: " 처 리 일 자 : 2025년07월11일~2025년07월11일 출 력 일 시 : 2025년12월04일17시01분"
|
||||
String line3 = allLines.get(2).trim();
|
||||
String line4 = allLines.get(3).trim();
|
||||
|
||||
String prgrmId = "";
|
||||
String prcsYmd = "";
|
||||
String otptDt = "";
|
||||
|
||||
// 프로그램 ID 추출 (: 기준으로 split)
|
||||
if (line3.contains(":")) {
|
||||
String[] parts = line3.split(":", 2);
|
||||
if (parts.length > 1) {
|
||||
prgrmId = parts[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 처리일자, 출력일시 추출
|
||||
if (line4.contains(":")) {
|
||||
String[] parts = line4.split(":", 2);
|
||||
if (parts.length > 1) {
|
||||
String rest = parts[1].trim();
|
||||
// "2025년07월11일~2025년07월11일 출 력 일 시 : 2025년12월04일17시01분" 형태
|
||||
if (rest.contains("출")) {
|
||||
int idx = rest.indexOf("출");
|
||||
prcsYmd = rest.substring(0, idx).trim();
|
||||
String rest2 = rest.substring(idx);
|
||||
if (rest2.contains(":")) {
|
||||
String[] parts2 = rest2.split(":", 2);
|
||||
if (parts2.length > 1) {
|
||||
otptDt = parts2[1].trim();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prcsYmd = rest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("헤더 정보 - 프로그램ID: {}, 처리일자: {}, 출력일시: {}", prgrmId, prcsYmd, otptDt);
|
||||
|
||||
// 8번째 라인부터 데이터 처리 (인덱스 7부터 시작)
|
||||
for (int i = 7; i < allLines.size(); i++) {
|
||||
String firstLine = allLines.get(i);
|
||||
|
||||
if (firstLine.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstLine.trim().startsWith("---")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 >= allLines.size()) {
|
||||
String errorMsg = String.format("[라인 %d] 데이터가 불완전합니다. 2줄 1세트 형식이 필요합니다.", i + 1);
|
||||
errorMessages.add(errorMsg);
|
||||
throw new MessageException(buildErrorMessage(errorMessages));
|
||||
}
|
||||
|
||||
String secondLine = allLines.get(i + 1);
|
||||
dataLineNumber++;
|
||||
|
||||
// 고정폭 파싱
|
||||
CarFfnlgTrgtIncmpVO vo = parseFixedWidthData(firstLine, secondLine, dataLineNumber, errorMessages, prgrmId, prcsYmd, otptDt);
|
||||
|
||||
if (vo == null) {
|
||||
throw new MessageException(buildErrorMessage(errorMessages));
|
||||
}
|
||||
|
||||
// 필수 필드 검증
|
||||
List<String> validationErrors = validateParsedData(dataLineNumber, vo);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
errorMessages.addAll(validationErrors);
|
||||
throw new MessageException(buildErrorMessage(errorMessages));
|
||||
}
|
||||
|
||||
// 차량번호+검사유효기간 중복 체크
|
||||
CarFfnlgTrgtIncmpVO checkVO = new CarFfnlgTrgtIncmpVO();
|
||||
checkVO.setVhclno(vo.getVhclno());
|
||||
checkVO.setInspVldPrd(vo.getInspVldPrd());
|
||||
int duplicateCount = mapper.checkDuplicateVhclno(checkVO);
|
||||
if (duplicateCount > 0) {
|
||||
String errorMsg = String.format("[데이터 %d] 중복된 차량번호+검사유효기간입니다. 차량번호: %s, 검사유효기간: %s",
|
||||
dataLineNumber, vo.getVhclno(), vo.getInspVldPrd());
|
||||
errorMessages.add(errorMsg);
|
||||
throw new MessageException(buildErrorMessage(errorMessages));
|
||||
}
|
||||
|
||||
// 업무 처리 상태 및 등록자 설정
|
||||
vo.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT);
|
||||
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
vo.setRgtr(rgtr);
|
||||
|
||||
// DB 저장
|
||||
int insertResult = mapper.insert(vo);
|
||||
|
||||
if (insertResult > 0) {
|
||||
successCount++;
|
||||
log.debug("데이터 저장 성공 [데이터 {}] - 차량번호: {}", dataLineNumber, vo.getVhclno());
|
||||
} else {
|
||||
String errorMsg = String.format("[데이터 %d] 데이터 저장 실패 - 차량번호: %s", dataLineNumber, vo.getVhclno());
|
||||
errorMessages.add(errorMsg);
|
||||
throw new MessageException(buildErrorMessage(errorMessages));
|
||||
}
|
||||
|
||||
// 2줄 1세트이므로 다음 줄 건너뛰기
|
||||
i++;
|
||||
}
|
||||
|
||||
log.info("PRN 파일 처리 완료 - 성공: {}건", successCount);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("successCount", successCount);
|
||||
result.put("failCount", 0);
|
||||
result.put("errorMessages", errorMessages);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("파일 검증 중 오류 발생: {}", e.getMessage());
|
||||
errorMessages.add(e.getMessage());
|
||||
result.put("success", false);
|
||||
result.put("successCount", 0);
|
||||
result.put("failCount", 0);
|
||||
result.put("errorMessages", errorMessages);
|
||||
} catch (MessageException e) {
|
||||
log.error("PRN 파일 업로드 중 오류 발생 - 전체 롤백 처리", e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("PRN 파일 업로드 중 예상치 못한 오류 발생", e);
|
||||
errorMessages.add("파일 업로드 중 오류가 발생했습니다: " + e.getMessage());
|
||||
throw new MessageException(buildErrorMessage(errorMessages), e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 목록을 EUC-KR 텍스트로 생성하여 다운로드용 바이트 배열을 반환
|
||||
*/
|
||||
@Override
|
||||
public byte[] generateEucKrDownloadBytes(CarFfnlgTrgtIncmpVO vo) {
|
||||
try {
|
||||
final String encoding = parseConfig.getEncoding() == null || parseConfig.getEncoding().trim().isEmpty()
|
||||
? "EUC-KR" : parseConfig.getEncoding().trim();
|
||||
|
||||
List<CarFfnlgTrgtIncmpVO> list = mapper.selectList(vo);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 헤더 구성
|
||||
sb.append("검사미필 과태료부과대상 리스트\r\n");
|
||||
sb.append("------------------------------------\r\n");
|
||||
sb.append("\r\n");
|
||||
|
||||
// 데이터 라인 생성
|
||||
for (CarFfnlgTrgtIncmpVO row : list) {
|
||||
String firstLine =
|
||||
padRightBytes(nvl(row.getNo()), 6, encoding) +
|
||||
padRightBytes(nvl(row.getVhclno()), 14, encoding) +
|
||||
padRightBytes(nvl(row.getOwnrNm()), 16, encoding) +
|
||||
padRightBytes(nvl(row.getCarNm()), 22, encoding) +
|
||||
padRightBytes(nvl(row.getUseStrhldAddr()), 62, encoding) +
|
||||
padRightBytes(nvl(row.getInspVldPrd()), 23, encoding);
|
||||
|
||||
sb.append(firstLine).append("\r\n");
|
||||
|
||||
String secondLine =
|
||||
padRightBytes("", 38, encoding) +
|
||||
padRightBytes(nvl(row.getRrno()), 16, encoding) +
|
||||
padRightBytes(nvl(row.getUseStrhldAddr()), 62, encoding);
|
||||
|
||||
sb.append(secondLine).append("\r\n");
|
||||
sb.append("\r\n");
|
||||
}
|
||||
|
||||
return sb.toString().getBytes(encoding);
|
||||
} catch (Exception e) {
|
||||
throw new MessageException("다운로드 파일 생성 중 오류: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택된 목록에 대해 API 호출 및 기본정보/등록원부 비교
|
||||
* 미필의 경우 부과일자 = 검사유효기간 종료일 + OM_DAY_CD의 D 코드값(145일)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> compareWithApi(List<Map<String, String>> targetList) {
|
||||
log.info("========== 미필 API 호출 및 비교 시작 ==========");
|
||||
log.info("선택된 데이터 건수: {}", targetList != null ? targetList.size() : 0);
|
||||
|
||||
if (targetList == null || targetList.isEmpty()) {
|
||||
throw new IllegalArgumentException("선택된 데이터가 없습니다.");
|
||||
}
|
||||
|
||||
List<Map<String, Object>> compareResults = new ArrayList<>();
|
||||
int successCount = 0;
|
||||
int productUseCount = 0;
|
||||
int transferCount = 0;
|
||||
int normalCount = 0;
|
||||
|
||||
// 가산일 조회 (OM_DAY_CD 코드의 D값)
|
||||
String plusDayStr = mapper.selectOmDayPlusDay();
|
||||
int plusDay = 145; // 기본값
|
||||
if (plusDayStr != null && !plusDayStr.isEmpty()) {
|
||||
try {
|
||||
plusDay = Integer.parseInt(plusDayStr);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("가산일 파싱 실패, 기본값 145 사용: {}", plusDayStr);
|
||||
}
|
||||
}
|
||||
log.info("부과일자 가산일: {}일", plusDay);
|
||||
|
||||
for (Map<String, String> target : targetList) {
|
||||
String carFfnlgTrgtIncmpId = target.get("carFfnlgTrgtIncmpId");
|
||||
String vhclno = target.get("vhclno");
|
||||
String inspVldPrd = target.get("inspVldPrd");
|
||||
|
||||
log.info("처리 중 - 차량번호: {}, 검사유효기간: {}", vhclno, inspVldPrd);
|
||||
|
||||
Map<String, Object> compareResult = new HashMap<>();
|
||||
compareResult.put("carFfnlgTrgtIncmpId", carFfnlgTrgtIncmpId);
|
||||
compareResult.put("vhclno", vhclno);
|
||||
|
||||
try {
|
||||
// 1. 기존 데이터 조회
|
||||
CarFfnlgTrgtIncmpVO existingData = new CarFfnlgTrgtIncmpVO();
|
||||
existingData.setCarFfnlgTrgtIncmpId(carFfnlgTrgtIncmpId);
|
||||
existingData = mapper.selectOne(existingData);
|
||||
|
||||
if (existingData == null) {
|
||||
String errorMsg = String.format("기존 데이터를 찾을 수 없습니다. 차량번호: %s", vhclno);
|
||||
log.error(errorMsg);
|
||||
throw new MessageException(errorMsg);
|
||||
}
|
||||
|
||||
// 2. 처리상태 검증 - 접수상태(01)가 아닌 경우 API 호출 불가
|
||||
if (!TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT.equals(existingData.getTaskPrcsSttsCd())) {
|
||||
String errorMsg = String.format("접수 상태(01)인 데이터만 API 호출이 가능합니다. 차량번호: %s, 현재 상태: %s",
|
||||
vhclno, existingData.getTaskPrcsSttsCd());
|
||||
log.error(errorMsg);
|
||||
throw new MessageException(errorMsg);
|
||||
}
|
||||
|
||||
// 3. 검사유효기간에서 부과일자 계산 (종료일 + 가산일)
|
||||
String levyCrtrYmd = calculateLevyCrtrYmdFromInspVldPrd(inspVldPrd, plusDay);
|
||||
existingData.setLevyCrtrYmd(levyCrtrYmd);
|
||||
log.info("부과일자 계산 완료 - 검사유효기간: {}, 부과일자: {}", inspVldPrd, levyCrtrYmd);
|
||||
|
||||
// 4. 비교 로직 실행
|
||||
String statusCode = comparisonOmService.executeComparison(existingData);
|
||||
|
||||
// 결과 처리
|
||||
if (statusCode != null) {
|
||||
if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE.equals(statusCode)) {
|
||||
productUseCount++;
|
||||
compareResult.put("processStatus", "상품용");
|
||||
compareResult.put("message", "상품용으로 처리되었습니다.");
|
||||
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER.equals(statusCode)) {
|
||||
transferCount++;
|
||||
compareResult.put("processStatus", "이첩");
|
||||
compareResult.put("message", "이첩으로 처리되었습니다.");
|
||||
} else if (TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED.equals(statusCode)) {
|
||||
normalCount++;
|
||||
compareResult.put("processStatus", "내사종결");
|
||||
compareResult.put("message", "내사종결로 처리되었습니다.");
|
||||
} else {
|
||||
normalCount++;
|
||||
compareResult.put("processStatus", "기타");
|
||||
compareResult.put("message", "기타 상태로 처리되었습니다.");
|
||||
}
|
||||
compareResult.put("success", true);
|
||||
successCount++;
|
||||
} else {
|
||||
normalCount++;
|
||||
compareResult.put("success", true);
|
||||
compareResult.put("message", "정상 처리되었습니다.");
|
||||
compareResult.put("processStatus", "정상");
|
||||
successCount++;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("데이터 비교 중 오류 발생 - 차량번호: {}, 전체 롤백 처리", vhclno, e);
|
||||
throw new MessageException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
compareResults.add(compareResult);
|
||||
}
|
||||
|
||||
Map<String, Object> resultData = new HashMap<>();
|
||||
resultData.put("compareResults", compareResults);
|
||||
resultData.put("totalCount", targetList.size());
|
||||
resultData.put("successCount", successCount);
|
||||
resultData.put("failCount", 0);
|
||||
resultData.put("productUseCount", productUseCount);
|
||||
resultData.put("transferCount", transferCount);
|
||||
resultData.put("normalCount", normalCount);
|
||||
|
||||
log.info("========== 미필 API 호출 및 비교 완료 ==========");
|
||||
log.info("성공: {}건, 상품용: {}건, 이첩: {}건, 정상: {}건",
|
||||
successCount, productUseCount, transferCount, normalCount);
|
||||
|
||||
return resultData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 정보를 일괄 저장
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public int saveCarFfnlgTrgtIncmps(CarFfnlgTrgtIncmpModifiedDataVO modifyData) {
|
||||
int result = 0;
|
||||
|
||||
// 1. 삭제된 행 처리
|
||||
List<CarFfnlgTrgtIncmpVO> deletedRows = modifyData.getDeletedRows();
|
||||
if (deletedRows != null && !deletedRows.isEmpty()) {
|
||||
for (CarFfnlgTrgtIncmpVO vo : deletedRows) {
|
||||
String dltr = SessionUtil.getUserId();
|
||||
if (dltr == null || dltr.isEmpty()) {
|
||||
throw new MessageException("로그인 정보가 없습니다.");
|
||||
}
|
||||
vo.setDltr(dltr);
|
||||
result += mapper.delete(vo);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 추가된 행 처리
|
||||
List<CarFfnlgTrgtIncmpVO> createdRows = modifyData.getCreatedRows();
|
||||
if (createdRows != null && !createdRows.isEmpty()) {
|
||||
for (CarFfnlgTrgtIncmpVO vo : createdRows) {
|
||||
String rgtr = SessionUtil.getUserId();
|
||||
if (rgtr == null || rgtr.isEmpty()) {
|
||||
throw new MessageException("로그인 정보가 없습니다.");
|
||||
}
|
||||
vo.setRgtr(rgtr);
|
||||
|
||||
if (vo.getTaskPrcsSttsCd() == null || vo.getTaskPrcsSttsCd().isEmpty()) {
|
||||
vo.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_01_RCPT);
|
||||
}
|
||||
|
||||
result += mapper.insert(vo);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 수정된 행 처리
|
||||
List<CarFfnlgTrgtIncmpVO> updatedRows = modifyData.getUpdatedRows();
|
||||
if (updatedRows != null && !updatedRows.isEmpty()) {
|
||||
for (CarFfnlgTrgtIncmpVO vo : updatedRows) {
|
||||
vo.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
result += mapper.updateTaskPrcsSttsCdAndRmrk(vo);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("과태료 대상 미필 일괄 저장 완료 - 처리 건수: {}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CarFfnlgTrgtIncmpExcelVO> selectListForExcel(CarFfnlgTrgtIncmpVO vo) {
|
||||
log.debug("과태료 대상 미필 목록 엑셀 다운로드용 조회 - 검색조건: {}", vo);
|
||||
return mapper.selectListForExcel(vo);
|
||||
}
|
||||
|
||||
// ================== 내부 유틸 메서드 ==================
|
||||
|
||||
private static String nvl(Object o) {
|
||||
if (o == null) return "";
|
||||
return o.toString();
|
||||
}
|
||||
|
||||
private static String padRightBytes(String s, int byteLen, String encoding) throws Exception {
|
||||
if (byteLen <= 0) return nvl(s);
|
||||
String v = nvl(s);
|
||||
byte[] b = v.getBytes(encoding);
|
||||
if (b.length == byteLen) return v;
|
||||
if (b.length > byteLen) {
|
||||
return truncateToBytes(v, byteLen, encoding);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(v);
|
||||
while (sb.toString().getBytes(encoding).length < byteLen) {
|
||||
sb.append(' ');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String truncateToBytes(String s, int byteLen, String encoding) throws Exception {
|
||||
if (s == null) return "";
|
||||
byte[] b = s.getBytes(encoding);
|
||||
if (b.length <= byteLen) return s;
|
||||
byte[] cut = new byte[byteLen];
|
||||
System.arraycopy(b, 0, cut, 0, byteLen);
|
||||
for (int len = byteLen; len > 0; len--) {
|
||||
try {
|
||||
return new String(cut, 0, len, encoding);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String buildErrorMessage(List<String> errorMessages) {
|
||||
if (errorMessages.isEmpty()) {
|
||||
return "파일 업로드 중 오류가 발생했습니다.";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("파일 업로드 실패 - 전체 롤백 처리되었습니다.\n\n");
|
||||
sb.append("[오류 상세 내역]\n");
|
||||
|
||||
int displayCount = Math.min(errorMessages.size(), 10);
|
||||
for (int i = 0; i < displayCount; i++) {
|
||||
sb.append(errorMessages.get(i)).append("\n");
|
||||
}
|
||||
|
||||
if (errorMessages.size() > 10) {
|
||||
sb.append("... 외 ").append(errorMessages.size() - 10).append("건\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 기반 바이트 단위 고정폭 데이터 파싱 (2줄 1세트)
|
||||
*/
|
||||
private CarFfnlgTrgtIncmpVO parseFixedWidthData(String firstLine, String secondLine,
|
||||
int dataLineNumber, List<String> errorMessages,
|
||||
String prgrmId, String prcsYmd, String otptDt) {
|
||||
try {
|
||||
CarFfnlgTrgtIncmpVO vo = new CarFfnlgTrgtIncmpVO();
|
||||
|
||||
String encoding = parseConfig.getEncoding();
|
||||
log.debug("[데이터 {}] 파싱 시작 - 인코딩: {}, 한글바이트: {}", dataLineNumber, encoding, parseConfig.getHangulByteSize());
|
||||
|
||||
// 첫 번째 줄 파싱
|
||||
byte[] firstBytes = firstLine.getBytes(encoding);
|
||||
int pos = 0;
|
||||
|
||||
// 번호
|
||||
int len = parseConfig.getFirstLineLength("no");
|
||||
String no = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 차량번호
|
||||
len = parseConfig.getFirstLineLength("vhclno");
|
||||
String vhclno = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 소유자명 (주민번호 빈칸 포함 32바이트)
|
||||
len = parseConfig.getFirstLineLength("ownr-nm");
|
||||
String ownrNm = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 자동차명
|
||||
len = parseConfig.getFirstLineLength("car-nm");
|
||||
String carNm = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 사용본거지주소
|
||||
len = parseConfig.getFirstLineLength("use-strhld-addr");
|
||||
String useStrhldAddr = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 검사유효기간
|
||||
len = parseConfig.getFirstLineLength("insp-vld-prd");
|
||||
String inspVldPrd = extractByteLength(firstBytes, pos, len, encoding).trim();
|
||||
|
||||
// 두 번째 줄 파싱
|
||||
byte[] secondBytes = secondLine.getBytes(encoding);
|
||||
pos = 0;
|
||||
|
||||
// 공백 스킵
|
||||
len = parseConfig.getSecondLineLength("skip");
|
||||
pos += len;
|
||||
|
||||
// 주민등록번호
|
||||
len = parseConfig.getSecondLineLength("rrno");
|
||||
String rrno = extractByteLength(secondBytes, pos, len, encoding).trim();
|
||||
pos += len;
|
||||
|
||||
// 사용본거지주소 (나머지)
|
||||
len = parseConfig.getSecondLineLength("use-strhld-addr");
|
||||
String useStrhldAddr2 = extractByteLength(secondBytes, pos, len, encoding).trim();
|
||||
|
||||
// 주소 합치기
|
||||
if (!useStrhldAddr2.isEmpty()) {
|
||||
useStrhldAddr = useStrhldAddr + " " + useStrhldAddr2;
|
||||
}
|
||||
|
||||
// VO 설정
|
||||
if (!no.isEmpty()) {
|
||||
try {
|
||||
vo.setNo(Integer.parseInt(no));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("번호 파싱 실패: {}", no);
|
||||
}
|
||||
}
|
||||
vo.setPrgrmId(prgrmId);
|
||||
vo.setPrcsYmd(prcsYmd);
|
||||
vo.setOtptDt(otptDt);
|
||||
vo.setVhclno(vhclno);
|
||||
vo.setOwnrNm(ownrNm);
|
||||
vo.setRrno(rrno);
|
||||
vo.setCarNm(carNm);
|
||||
vo.setUseStrhldAddr(useStrhldAddr);
|
||||
vo.setInspVldPrd(inspVldPrd);
|
||||
|
||||
log.debug("[데이터 {}] 파싱 완료", dataLineNumber);
|
||||
|
||||
return vo;
|
||||
|
||||
} catch (Exception e) {
|
||||
String errorMsg = String.format("[데이터 %d] 파싱 중 오류 발생 - %s", dataLineNumber, e.getMessage());
|
||||
errorMessages.add(errorMsg);
|
||||
log.error("데이터 {} 파싱 중 오류", dataLineNumber, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String extractByteLength(byte[] bytes, int pos, int length, String encoding) {
|
||||
try {
|
||||
if (pos < 0) pos = 0;
|
||||
if (pos >= bytes.length) return "";
|
||||
|
||||
int actualLength;
|
||||
if (length < 0) {
|
||||
actualLength = bytes.length - pos;
|
||||
} else {
|
||||
actualLength = Math.min(length, bytes.length - pos);
|
||||
}
|
||||
|
||||
if (actualLength <= 0) return "";
|
||||
|
||||
byte[] extracted = new byte[actualLength];
|
||||
System.arraycopy(bytes, pos, extracted, 0, actualLength);
|
||||
|
||||
return new String(extracted, encoding);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("바이트 추출 중 오류 발생 - pos: {}, length: {}, encoding: {}", pos, length, encoding, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 파싱된 데이터 유효성 검증
|
||||
*/
|
||||
private List<String> validateParsedData(int dataLineNumber, CarFfnlgTrgtIncmpVO vo) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
String vhclno = vo.getVhclno() != null ? vo.getVhclno() : "알 수 없음";
|
||||
|
||||
// 1. 차량번호 검증
|
||||
if (vo.getVhclno() == null || vo.getVhclno().isEmpty()) {
|
||||
errors.add(String.format("[데이터 %d] 차량번호가 누락되었습니다.", dataLineNumber));
|
||||
} else if (vo.getVhclno().length() > 30) {
|
||||
errors.add(String.format("[데이터 %d] 차량번호가 너무 깁니다. 차량번호: %s (최대 30자)", dataLineNumber, vo.getVhclno()));
|
||||
}
|
||||
|
||||
// 2. 소유자명 검증
|
||||
if (vo.getOwnrNm() == null || vo.getOwnrNm().isEmpty()) {
|
||||
errors.add(String.format("[데이터 %d] 소유자명이 누락되었습니다. 차량번호: %s", dataLineNumber, vhclno));
|
||||
} else if (vo.getOwnrNm().length() > 75) {
|
||||
errors.add(String.format("[데이터 %d] 소유자명이 너무 깁니다. 소유자명: %s (최대 75자), 차량번호: %s",
|
||||
dataLineNumber, vo.getOwnrNm(), vhclno));
|
||||
}
|
||||
|
||||
// 3. 주민등록번호 검증
|
||||
if (vo.getRrno() == null || vo.getRrno().isEmpty()) {
|
||||
errors.add(String.format("[데이터 %d] 주민등록번호가 누락되었습니다. 차량번호: %s", dataLineNumber, vhclno));
|
||||
} else if (vo.getRrno().length() > 100) {
|
||||
errors.add(String.format("[데이터 %d] 주민등록번호가 너무 깁니다. 주민번호 길이: %d (최대 100자), 차량번호: %s",
|
||||
dataLineNumber, vo.getRrno().length(), vhclno));
|
||||
}
|
||||
|
||||
// 4. 검사유효기간 검증
|
||||
if (vo.getInspVldPrd() == null || vo.getInspVldPrd().isEmpty()) {
|
||||
errors.add(String.format("[데이터 %d] 검사유효기간이 누락되었습니다. 차량번호: %s", dataLineNumber, vhclno));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검사유효기간에서 부과일자 계산
|
||||
* 예: "2023-07-12~2025-07-11" -> 종료일(2025-07-11) + 가산일(145일) = 20251203
|
||||
*/
|
||||
private String calculateLevyCrtrYmdFromInspVldPrd(String inspVldPrd, int plusDay) {
|
||||
if (inspVldPrd == null || inspVldPrd.isEmpty()) {
|
||||
throw new IllegalArgumentException("검사유효기간이 없습니다.");
|
||||
}
|
||||
|
||||
// "2023-07-12~2025-07-11" 형식에서 종료일 추출
|
||||
String[] parts = inspVldPrd.split("~");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("검사유효기간 형식이 올바르지 않습니다: " + inspVldPrd);
|
||||
}
|
||||
|
||||
String endDateStr = parts[1].trim().replace("-", "");
|
||||
|
||||
try {
|
||||
LocalDate endDate = LocalDate.parse(endDateStr, DATE_FORMATTER);
|
||||
LocalDate levyDate = endDate.plusDays(plusDay);
|
||||
return levyDate.format(DATE_FORMATTER);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("검사유효기간 종료일 파싱 실패: " + endDateStr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,279 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl;
|
||||
|
||||
import egovframework.util.DateUtil;
|
||||
import egovframework.util.StringUtil;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 비교 비고(Remark) 생성 유틸리티 클래스
|
||||
*
|
||||
* <p>각 비교 로직별 비고 문자열을 생성하는 메서드를 제공합니다.</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
public class ComparisonOmRemarkBuilder {
|
||||
|
||||
/**
|
||||
* 상품용 비고 생성 - 미필
|
||||
*
|
||||
* @param step1Record Step 1 API 응답 (부과일자 기준 소유자명)
|
||||
* @param step4Record Step 4 API 응답 (명의이전 시점 소유자명)
|
||||
* @param ledgerRecord 조건에 맞는 갑부 레코드
|
||||
* @param inspVldPrdEnd 검사유효기간 종료일
|
||||
* @param levyCrtrYmd 부과일자 (검사유효기간 종료일 + 145일)
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildProductUseRemark(
|
||||
NewBasicResponse.Record step1Record,
|
||||
NewBasicResponse.Record step4Record,
|
||||
NewLedgerResponse.Record ledgerRecord,
|
||||
String inspVldPrdEnd,
|
||||
String levyCrtrYmd) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("상품용 - 미필검사\n");
|
||||
|
||||
// 1. 부과일자 기준 소유자 정보
|
||||
sb.append("\n■ 부과일자 기준 소유자정보\n");
|
||||
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
|
||||
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
|
||||
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
|
||||
|
||||
// 2. 명의이전 시점 소유자 정보
|
||||
sb.append("\n■ 명의이전 시점 소유자정보\n");
|
||||
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
|
||||
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
|
||||
// 3. 갑부 상세 정보 (명의이전 이력)
|
||||
sb.append("\n■ 갑부 상세 (명의이전 이력)\n");
|
||||
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
|
||||
|
||||
// 4. 검사유효기간 정보
|
||||
sb.append("\n■ 검사유효기간 정보\n");
|
||||
sb.append(" - 검사유효기간 종료일: ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 상품용-변경등록 비고 생성 - 미필
|
||||
*
|
||||
* @param step1Record Step 1 API 응답 (부과일자 기준 소유자명)
|
||||
* @param step4Record Step 4 API 응답 (변경등록 시점 소유자명)
|
||||
* @param ledgerRecord 조건에 맞는 갑부 레코드 (변경등록 레코드)
|
||||
* @param inspVldPrdEnd 검사유효기간 종료일
|
||||
* @param levyCrtrYmd 부과일자 (검사유효기간 종료일 + 145일)
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildProductUseChangeRemark(
|
||||
NewBasicResponse.Record step1Record,
|
||||
NewBasicResponse.Record step4Record,
|
||||
NewLedgerResponse.Record ledgerRecord,
|
||||
String inspVldPrdEnd,
|
||||
String levyCrtrYmd) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("상품용 - 변경등록 - 미필\n");
|
||||
|
||||
// 1. 부과일자 기준 소유자 정보
|
||||
sb.append("\n■ 부과일자 기준 소유자정보\n");
|
||||
sb.append(" - 소유자명: ").append(StringUtil.nvl(step1Record.getRprsOwnrNm())).append("\n");
|
||||
sb.append(" - 차대번호: ").append(StringUtil.nvl(step1Record.getVin())).append("\n");
|
||||
|
||||
// 2. 변경등록 시점 소유자 정보
|
||||
sb.append("\n■ 변경등록 시점 소유자정보\n");
|
||||
sb.append(" - 소유자명: ").append(StringUtil.nvl(step4Record.getRprsOwnrNm())).append("\n");
|
||||
sb.append(" - 조회일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
|
||||
// 3. 갑부 상세 정보 (변경등록 이력)
|
||||
sb.append("\n■ 갑부 상세 (변경등록 이력)\n");
|
||||
sb.append(" - 변경일자: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
sb.append(" - 변경업무코드: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeCd())).append("\n");
|
||||
sb.append(" - 변경업무명: ").append(StringUtil.nvl(ledgerRecord.getChgTaskSeNm())).append("\n");
|
||||
sb.append(" - 특별사항: ").append(StringUtil.nvl(ledgerRecord.getSpcablMttr())).append("\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 상품용 비고 생성 - 미필 (부과일자 소유자가 상품용이 아니지만, 명의이전 후 상품용인 경우)
|
||||
*
|
||||
* 비고 형식:
|
||||
* 명의이전(25.9.3.) 이전소유자 상품용
|
||||
* 22루2283
|
||||
* 검사유효기간 시작일 - 종료일
|
||||
* 부과일자 일자
|
||||
* 명의이전 일자
|
||||
* 상품용 일자
|
||||
*
|
||||
* @param step1Record Step 1 API 응답 (부과일자 기준 소유자명)
|
||||
* @param step4Record Step 4 API 응답 (명의이전 시점 소유자명 = 상품용)
|
||||
* @param ledgerRecord 조건에 맞는 갑부 레코드 (명의이전 레코드)
|
||||
* @param vhclno 차량번호
|
||||
* @param levyCrtrYmd 부과일자 (검사유효기간 종료일 + 145일)
|
||||
* @param inspVldPrdStart 검사유효기간 시작일
|
||||
* @param inspVldPrdEnd 검사유효기간 종료일
|
||||
* @param daysBetween 일수차이
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildProductCloseLevyRemark(
|
||||
NewBasicResponse.Record step1Record,
|
||||
NewBasicResponse.Record step4Record,
|
||||
NewLedgerResponse.Record ledgerRecord,
|
||||
String vhclno,
|
||||
String levyCrtrYmd,
|
||||
String inspVldPrdStart,
|
||||
String inspVldPrdEnd,
|
||||
long daysBetween) {
|
||||
|
||||
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
|
||||
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
|
||||
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 첫 줄: 명의이전(25.9.3.) 이전소유자 상품용
|
||||
sb.append("명의이전(").append(chgYmdFormatted).append(") 이전소유자 상품용").append("\n");
|
||||
|
||||
// 둘째 줄: 차량번호
|
||||
sb.append(StringUtil.nvl(vhclno)).append("\n");
|
||||
|
||||
// 셋째 줄: 검사유효기간 시작일 - 종료일
|
||||
sb.append(" - 검사유효기간: ").append(DateUtil.formatDateString(inspVldPrdStart))
|
||||
.append(" - ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
|
||||
|
||||
// 넷째 줄: 부과일자 일자
|
||||
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
|
||||
|
||||
// 다섯째 줄: 명의이전 일자
|
||||
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
|
||||
// 여섯째 줄: 상품용 일자 (명의이전 일자와 동일)
|
||||
sb.append(" - 상품용: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
|
||||
// 일곱째 줄: 일수차이
|
||||
sb.append("일수차이: ").append(daysBetween).append("일");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 명의이전 비고 생성 - 미필 (내사종결 또는 날짜 수정 후 부과)
|
||||
*
|
||||
* 비고 형식:
|
||||
* 명의이전(25.9.3.)
|
||||
* 22루2283
|
||||
* 검사유효기간 시작일 - 종료일
|
||||
* 부과일자 일자
|
||||
* 명의이전 일자
|
||||
*
|
||||
* @param step1Record Step 1 API 응답 (부과일자 기준 소유자명)
|
||||
* @param step4Record Step 4 API 응답 (명의이전 시점 소유자명)
|
||||
* @param ledgerRecord 조건에 맞는 갑부 레코드 (명의이전 레코드)
|
||||
* @param vhclno 차량번호
|
||||
* @param levyCrtrYmd 부과일자 (검사유효기간 종료일 + 145일)
|
||||
* @param inspVldPrdStart 검사유효기간 시작일
|
||||
* @param inspVldPrdEnd 검사유효기간 종료일
|
||||
* @param daysBetween 일수차이
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildOwnerChangeRemark(NewBasicResponse.Record step1Record,
|
||||
NewBasicResponse.Record step4Record,
|
||||
NewLedgerResponse.Record ledgerRecord,
|
||||
String vhclno,
|
||||
String levyCrtrYmd,
|
||||
String inspVldPrdStart,
|
||||
String inspVldPrdEnd,
|
||||
long daysBetween) {
|
||||
|
||||
// 날짜 포맷 변환 (YYYYMMDD -> YY.M.D)
|
||||
String chgYmdFormatted = DateUtil.formatToShortDate(ledgerRecord.getChgYmd());
|
||||
String step1wnerName = StringUtil.nvl(step1Record.getRprsOwnrNm());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 첫 줄: 명의이전(25.9.3.)
|
||||
sb.append("명의이전(").append(chgYmdFormatted).append(")").append("\n");
|
||||
|
||||
// 둘째 줄: 차량번호
|
||||
sb.append(StringUtil.nvl(vhclno)).append("\n");
|
||||
|
||||
// 셋째 줄: 검사유효기간 시작일 - 종료일
|
||||
sb.append(" - 검사유효기간: ").append(DateUtil.formatDateString(inspVldPrdStart))
|
||||
.append(" - ").append(DateUtil.formatDateString(inspVldPrdEnd)).append("\n");
|
||||
|
||||
// 넷째 줄: 부과일자 일자
|
||||
sb.append(" - 부과일자: ").append(DateUtil.formatDateString(levyCrtrYmd)).append("\n");
|
||||
|
||||
// 다섯째 줄: 명의이전 일자
|
||||
sb.append(" - 명의이전: ").append(DateUtil.formatDateString(ledgerRecord.getChgYmd())).append("\n");
|
||||
|
||||
// 일곱째 줄: 일수차이
|
||||
sb.append("일수차이: ").append(daysBetween).append("일");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 이첩 비고 생성 - Case 이첩-1 (부과일자 사용본거지)
|
||||
*
|
||||
* @param sggNm 시군구명
|
||||
* @param userOrg4 사용자 조직코드 앞 4자리
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildTransferCase1Remark(String sggNm, String userOrg4) {
|
||||
return String.format("%s, 부과일자사용본거지, [사용자 조직코드 앞 4자리: %s, 법정동명: %s]",
|
||||
sggNm, userOrg4, sggNm);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이첩 비고 생성 - Case 이첩-2 (145일 도래지)
|
||||
*
|
||||
* @param sggNm 시군구명
|
||||
* @param legalDong4 법정동코드 앞 4자리
|
||||
* @return 비고 문자열
|
||||
*/
|
||||
public static String buildTransferCase2Remark(String sggNm, String legalDong4) {
|
||||
return String.format("%s, 145일 도래지, [법정동코드: %s, 법정동명: %s]",
|
||||
sggNm, legalDong4, sggNm);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록원부 갑부 레코드 상세 정보 생성
|
||||
*
|
||||
* @param record 갑부 레코드
|
||||
* @return 상세 정보 문자열
|
||||
*/
|
||||
public static String buildLedgerRecordDetail(NewLedgerResponse.Record record) {
|
||||
if (record == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder detail = new StringBuilder();
|
||||
|
||||
// 변경 정보
|
||||
StringUtil.appendIfNotEmpty(detail, "변경업무구분코드", record.getChgTaskSeCd());
|
||||
StringUtil.appendIfNotEmpty(detail, "변경업무구분명", record.getChgTaskSeNm());
|
||||
StringUtil.appendIfNotEmpty(detail, "변경일자", DateUtil.formatDateString(record.getChgYmd()));
|
||||
|
||||
// 주요 정보
|
||||
StringUtil.appendIfNotEmpty(detail, "주요번호", record.getMainNo());
|
||||
StringUtil.appendIfNotEmpty(detail, "일련번호", record.getSno());
|
||||
StringUtil.appendIfNotEmpty(detail, "특별사항", record.getSpcablMttr());
|
||||
|
||||
// 명의자 정보
|
||||
StringUtil.appendIfNotEmpty(detail, "명의자명", record.getHshldrNm());
|
||||
StringUtil.appendIfNotEmpty(detail, "명의자식별번호", StringUtil.maskIdecno(record.getHshldrIdecno()));
|
||||
|
||||
// 기타
|
||||
StringUtil.appendIfNotEmpty(detail, "신청접수번호", record.getAplyRcptNo());
|
||||
StringUtil.appendIfNotEmpty(detail, "차량관리번호", record.getVhmno());
|
||||
StringUtil.appendIfNotEmpty(detail, "원부그룹번호", record.getLedgerGroupNo());
|
||||
StringUtil.appendIfNotEmpty(detail, "원부개별번호", record.getLedgerIndivNo());
|
||||
StringUtil.appendIfNotEmpty(detail, "상세일련번호", record.getDtlSn());
|
||||
|
||||
return detail.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.ComparisonOmService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 과태료 대상 미필 비교 서비스 구현체
|
||||
*
|
||||
* <p>각 비교 로직을 독립적인 체커 클래스로 분리하여 관리합니다.</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ComparisonOmServiceImpl extends EgovAbstractServiceImpl implements ComparisonOmService {
|
||||
|
||||
private final ProductUseOmChecker productUseOmChecker;
|
||||
private final ProductUseOmChangeChecker productUseOmChangeChecker;
|
||||
private final ProductCloseWithin31OmChecker productCloseWithin31OmChecker;
|
||||
private final OwnerCloseWithin31OmChecker ownerCloseWithin31OmChecker;
|
||||
private final ProductLevyOver31OmChecker productLevyOver31OmChecker;
|
||||
private final OwnerLevyOver31OmChecker ownerLevyOver31OmChecker;
|
||||
private final TransferOmChecker transferOmChecker;
|
||||
|
||||
/**
|
||||
* 비교 로직 메인 메서드
|
||||
*
|
||||
* <p>순차적으로 각 비교 체커를 실행하고, 하나라도 적용되면 즉시 종료합니다.</p>
|
||||
* <p>미필의 경우 부과일자(levyCrtrYmd)가 미리 계산되어 existingData에 포함됩니다.</p>
|
||||
*/
|
||||
@Override
|
||||
public String executeComparison(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd();
|
||||
log.info("========== 미필 비교 로직 시작: {}, 부과일자: {} ==========", vhclno, levyCrtrYmd);
|
||||
|
||||
// ========== 1. 상품용 체크 - api-1번호출.소유자명.contains("상품용") ==========
|
||||
String productUseResult = productUseOmChecker.check(existingData);
|
||||
if (productUseResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (상품용): {} ==========", vhclno);
|
||||
return productUseResult;
|
||||
}
|
||||
|
||||
// ========== 2. 상품용 체크 - api-1번호출.소유자명.contains("상품용-변경등록") ==========
|
||||
String productUseChangeResult = productUseOmChangeChecker.check(existingData);
|
||||
if (productUseChangeResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (상품용-변경등록): {} ==========", vhclno);
|
||||
return productUseChangeResult;
|
||||
}
|
||||
|
||||
// ========== 3. 내사종결 체크 - 명의이전 이전소유자 상품용, 31일 이내 ==========
|
||||
String investigationClosedByProductResult = productCloseWithin31OmChecker.check(existingData);
|
||||
if (investigationClosedByProductResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (내사종결 - 명의이전 이전소유자 상품용, 31일 이내): {} ==========", vhclno);
|
||||
return investigationClosedByProductResult;
|
||||
}
|
||||
|
||||
// ========== 4. 내사종결 체크 - 명의이전, 31일 이내 ==========
|
||||
String investigationClosedByOwnerChangeResult = ownerCloseWithin31OmChecker.check(existingData);
|
||||
if (investigationClosedByOwnerChangeResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (내사종결 - 명의이전, 31일 이내): {} ==========", vhclno);
|
||||
return investigationClosedByOwnerChangeResult;
|
||||
}
|
||||
|
||||
// ========== 5. 날짜 수정 후 부과 체크 - 명의이전 이전소유자 상품용, 31일 초과 ==========
|
||||
String dateModifiedLevyByProductResult = productLevyOver31OmChecker.check(existingData);
|
||||
if (dateModifiedLevyByProductResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (날짜 수정 후 부과 - 명의이전 이전소유자, 31일 초과): {} ==========", vhclno);
|
||||
return dateModifiedLevyByProductResult;
|
||||
}
|
||||
|
||||
// ========== 6. 날짜 수정 후 부과 체크 - 명의이전, 31일 초과 ==========
|
||||
String dateModifiedLevyByOwnerChangeOverResult = ownerLevyOver31OmChecker.check(existingData);
|
||||
if (dateModifiedLevyByOwnerChangeOverResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (날짜 수정 후 부과 - 명의이전, 31일 초과): {} ==========", vhclno);
|
||||
return dateModifiedLevyByOwnerChangeOverResult;
|
||||
}
|
||||
|
||||
// ========== 7. 이첩 체크 ==========
|
||||
String transferResult = transferOmChecker.check(existingData);
|
||||
if (transferResult != null) {
|
||||
log.info("========== 미필 비교 로직 종료 (이첩): {} ==========", vhclno);
|
||||
return transferResult;
|
||||
}
|
||||
|
||||
log.info("========== 미필 비교 로직 종료 (미적용): {} ==========", vhclno);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 미필 비교 로직 추상 클래스
|
||||
*
|
||||
* <p>공통 의존성 및 헬퍼 메서드를 제공합니다.</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractComparisonOmChecker implements ComparisonOmChecker {
|
||||
|
||||
protected final CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper;
|
||||
protected final ExternalVehicleApiService apiService;
|
||||
protected final VmisCarBassMatterInqireLogService bassMatterLogService;
|
||||
protected final VmisCarLedgerFrmbkLogService ledgerLogService;
|
||||
|
||||
protected static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
/**
|
||||
* 명의이전 또는 상품용 관련 일수 기준값 (일)
|
||||
* 명의이전일자 ~ 검사일 사이의 일수가 이 값 이하면 내사종결, 초과하면 날짜 수정 후 부과
|
||||
*/
|
||||
protected static final int DAYS_THRESHOLD = 31;
|
||||
|
||||
/**
|
||||
* 자동차기본정보 요청 객체 생성
|
||||
* 미필의 경우 levyCrtrYmd = 검사유효기간 종료일 + 145일
|
||||
*
|
||||
* @param vhrno 차량번호
|
||||
* @param vin 차대번호
|
||||
* @param levyCrtrYmd 부과일자 (미필: 검사유효기간 종료일 + 145일)
|
||||
* @return NewBasicRequest
|
||||
*/
|
||||
protected NewBasicRequest createBasicRequest(String vhrno, String vin, String levyCrtrYmd) {
|
||||
NewBasicRequest request = new NewBasicRequest();
|
||||
|
||||
NewBasicRequest.Record record = new NewBasicRequest.Record();
|
||||
record.setLevyCrtrYmd(levyCrtrYmd);
|
||||
|
||||
if (vhrno != null) {
|
||||
record.setVhrno(vhrno);
|
||||
record.setInqSeCd("3"); // 3: 자동차번호
|
||||
} else if (vin != null) {
|
||||
record.setVin(vin);
|
||||
record.setInqSeCd("2"); // 2: 차대번호
|
||||
}
|
||||
|
||||
request.setRecord(java.util.Arrays.asList(record));
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동차등록원부(갑) 요청 객체 생성
|
||||
*
|
||||
* @param vhrno 차량번호
|
||||
* @param ownerNm 성명
|
||||
* @param idecno 주민번호
|
||||
* @param legalDongCd 법정동코드
|
||||
* @return NewLedgerRequest
|
||||
*/
|
||||
protected NewLedgerRequest createLedgerRequest(String vhrno, String ownerNm, String idecno, String legalDongCd) {
|
||||
NewLedgerRequest request = new NewLedgerRequest();
|
||||
|
||||
// 차량번호
|
||||
request.setVhrno(vhrno);
|
||||
|
||||
// 민원인 정보
|
||||
request.setCvlprNm(ownerNm);
|
||||
request.setCvlprIdecno(idecno);
|
||||
request.setCvlprStdgCd(legalDongCd);
|
||||
|
||||
// 개인정보공개 (1:소유자공개)
|
||||
request.setPrvcRls("1");
|
||||
|
||||
// 경로구분코드 (고정값 3)
|
||||
request.setPathSeCd("3");
|
||||
|
||||
// 내역표시 (1:전체내역)
|
||||
request.setDsctnIndct("1");
|
||||
|
||||
// 조회구분코드 (1:열람)
|
||||
request.setInqSeCd("1");
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
|
||||
/**
|
||||
* 미필 비교 로직 인터페이스
|
||||
*
|
||||
* <p>각 비교 로직 체커는 이 인터페이스를 구현합니다.</p>
|
||||
* <p>미필의 경우 부과일자(levyCrtrYmd)가 existingData에 포함되어 전달됩니다.</p>
|
||||
*/
|
||||
public interface ComparisonOmChecker {
|
||||
|
||||
/**
|
||||
* 비교 로직 실행
|
||||
*
|
||||
* @param existingData 과태료 대상 미필 데이터 (levyCrtrYmd 포함)
|
||||
* @return 처리상태코드 또는 null (미적용)
|
||||
*/
|
||||
String check(CarFfnlgTrgtIncmpVO existingData);
|
||||
}
|
||||
@ -0,0 +1,236 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 4. 내사종결 검증 - 순수 명의이전 (31일 이내) (미필)
|
||||
*
|
||||
* <p>부과일자 소유자가 상품용이 아니고, 명의이전 전 소유자도 상품용이 아닌 경우</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OwnerCloseWithin31OmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public OwnerCloseWithin31OmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[내사종결-명의이전-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
String step1RprsvOwnrIdecno = step1Record.getRprsvOwnrIdecno(); // 부과일자 기준 대표소유자 회원번호
|
||||
|
||||
log.info("[내사종결-명의이전-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 부과일자 소유자가 상품용 아님
|
||||
if (step1OwnerName != null && step1OwnerName.contains("상품용")) {
|
||||
log.debug("[내사종결-명의이전-미필] 부과일자 소유자가 상품용 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전-미필] 부과일자 소유자가 상품용 아님 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[내사종결-명의이전-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[내사종결-명의이전-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[내사종결-명의이전-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[내사종결-명의이전-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[내사종결-명의이전-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 검사유효기간 내 명의이전 레코드 찾기 (가장 최근 일자) ==========
|
||||
log.info("[내사종결-명의이전-미필] 갑부 상세 레코드 검색 시작 - 검사유효기간 시작일: {}, 검사유효기간 종료일: {}", inspVldPrdStart, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate inspVldPrdStartDate = DateUtil.parseDate(inspVldPrdStart);
|
||||
LocalDate inspVldPrdEndDate = DateUtil.parseDate(inspVldPrdEnd);
|
||||
LocalDate latestChgDate = null;
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "11" (명의이전)
|
||||
if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: 검사유효기간 시작일 <= CHG_YMD <= 검사유효기간 종료일
|
||||
if ((chgDate.isEqual(inspVldPrdStartDate) || chgDate.isAfter(inspVldPrdStartDate)) &&
|
||||
(chgDate.isEqual(inspVldPrdEndDate) || chgDate.isBefore(inspVldPrdEndDate))) {
|
||||
|
||||
// 가장 최근 일자 선택
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
targetRecord = record;
|
||||
latestChgDate = chgDate;
|
||||
log.debug("[내사종결-명의이전-미필] 검사유효기간 내 명의이전 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[내사종결-명의이전-미필] 검사유효기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[내사종결-명의이전-미필] 검사유효기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== 명의이전일자 ~ 부과일자 사이의 일수 계산 ==========
|
||||
LocalDate chgDate = DateUtil.parseDate(targetChgYmd);
|
||||
LocalDate levyDate = DateUtil.parseDate(levyCrtrYmd);
|
||||
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, levyDate);
|
||||
|
||||
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
|
||||
log.debug("[내사종결-명의이전-미필] 명의이전일자가 부과일자의 {}일 이내가 아님 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetChgYmd, levyCrtrYmd, daysBetween);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전-미필] 명의이전일자가 부과일자의 {}일 이내 확인 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetChgYmd, levyCrtrYmd, daysBetween);
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
|
||||
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetDate.format(DATE_FORMATTER));
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetDate.format(DATE_FORMATTER));
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
String step4RprsvOwnrIdecno = step4Record.getRprsvOwnrIdecno(); // 대표소유자 회원번호
|
||||
|
||||
log.info("[내사종결-명의이전-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// 검사유효기간내 명의변경 소유자와 부과일자의 소유자가 같냐
|
||||
if (step4OwnerName == null || !step4RprsvOwnrIdecno.contains(step1RprsvOwnrIdecno)) {
|
||||
log.debug("[내사종결-명의이전-미필] 명의이전 전 소유자가 상품용 - Step4 소유자명: {}", step4OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전-미필] 명의이전 전 소유자가 상품용 아님 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildOwnerChangeRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[내사종결-명의이전-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[내사종결-명의이전-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[내사종결-명의이전-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,236 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 6. 날짜 수정 후 부과 검증 - 순수 명의이전 (31일 초과) (미필)
|
||||
*
|
||||
* <p>부과일자 소유자가 상품용이 아니고, 명의이전 전 소유자도 상품용이 아니며, 31일 초과인 경우</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OwnerLevyOver31OmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public OwnerLevyOver31OmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
String step1RprsvOwnrIdecno = step1Record.getRprsvOwnrIdecno(); // 부과일자 기준 대표소유자 회원번호
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 부과일자 소유자가 상품용 아님
|
||||
if (step1OwnerName != null && step1OwnerName.contains("상품용")) {
|
||||
log.debug("[날짜수정후부과-명의이전-미필] 부과일자 소유자가 상품용 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] 부과일자 소유자가 상품용 아님 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[날짜수정후부과-명의이전-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[날짜수정후부과-명의이전-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 검사유효기간 내 명의이전 레코드 찾기 ==========
|
||||
log.info("[날짜수정후부과-명의이전-미필] 갑부 상세 레코드 검색 시작 - 검사유효기간 시작일: {}, 검사유효기간 종료일: {}", inspVldPrdStart, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate inspVldPrdStartDate = DateUtil.parseDate(inspVldPrdStart);
|
||||
LocalDate inspVldPrdEndDate = DateUtil.parseDate(inspVldPrdEnd);
|
||||
LocalDate latestChgDate = null;
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "11" (명의이전)
|
||||
if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: 검사유효기간 시작일 <= CHG_YMD <= 검사유효기간 종료일
|
||||
if ((chgDate.isEqual(inspVldPrdStartDate) || chgDate.isAfter(inspVldPrdStartDate)) &&
|
||||
(chgDate.isEqual(inspVldPrdEndDate) || chgDate.isBefore(inspVldPrdEndDate))) {
|
||||
|
||||
// 가장 최근 일자 선택
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
targetRecord = record;
|
||||
latestChgDate = chgDate;
|
||||
log.debug("[내사종결-명의이전-미필] 검사유효기간 내 명의이전 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[날짜수정후부과-명의이전-미필] 검사유효기간 내 명의이전 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[날짜수정후부과-명의이전-미필] 검사유효기간 내 명의이전 발견! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== 명의이전일자 ~ 부과일자 사이의 일수 계산 ==========
|
||||
LocalDate chgDate = DateUtil.parseDate(targetChgYmd);
|
||||
LocalDate levyDate = DateUtil.parseDate(levyCrtrYmd);
|
||||
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(chgDate, levyDate);
|
||||
|
||||
if (daysBetween <= DAYS_THRESHOLD) {
|
||||
log.debug("[날짜수정후부과-명의이전-미필] 명의이전일자가 부과일자의 {}일 이내임 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetChgYmd, levyCrtrYmd, daysBetween);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] 명의이전일자가 부과일자의 {}일 초과 확인 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetChgYmd, levyCrtrYmd, daysBetween);
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
|
||||
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetDate.format(DATE_FORMATTER));
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetDate.format(DATE_FORMATTER));
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
String step4RprsvOwnrIdecno = step4Record.getRprsvOwnrIdecno(); // 대표소유자 회원번호
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// 검사유효기간내 명의변경 소유자와 부과일자의 소유자가 같냐
|
||||
if (step4OwnerName == null || !step4RprsvOwnrIdecno.contains(step1RprsvOwnrIdecno)) {
|
||||
log.debug("[내사종결-명의이전-미필] 명의이전 전 소유자가 상품용 - Step4 소유자명: {}", step4OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] 명의이전 전 소유자가 상품용 아님 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildOwnerChangeRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[날짜수정후부과-명의이전-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[날짜수정후부과-명의이전-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[날짜수정후부과-명의이전-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 3. 내사종결 검증 - 명의이전 이전소유자 상품용, 31일 이내 (미필)
|
||||
*
|
||||
* <p>명의이전 이전 소유자가 상품용이고, 명의이전일자가 부과일자의 31일 이내인 경우</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProductCloseWithin31OmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public ProductCloseWithin31OmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전 상품용-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 조건 1: 소유자명에 "상품용" 포함 여부 확인
|
||||
if (step1OwnerName == null || step1OwnerName.contains("상품용")) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전 상품용-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[내사종결-명의이전 상품용-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 조건에 맞는 레코드 찾기 ==========
|
||||
log.info("[내사종결-명의이전 상품용-미필] 갑부 상세 레코드 검색 시작 - 부과일자: {}, 검사유효기간 종료일: {}", levyCrtrYmd, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate latestChgDate = null;
|
||||
LocalDate levyDate = DateUtil.parseDate(levyCrtrYmd);
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "11" (명의이전)
|
||||
if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: CHG_YMD <= 부과일자
|
||||
if (chgDate.isAfter(levyDate)) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] CHG_YMD > 부과일자 - 변경일자: {}, 부과일자: {}", chgYmd, levyCrtrYmd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 가장 마지막 일자 찾기
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
latestChgDate = chgDate;
|
||||
targetRecord = record;
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 조건: 가장 마지막 명의이전일자가 부과일자의 기준일수 이내인지 확인
|
||||
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, levyDate);
|
||||
if (daysBetween < 0 || daysBetween > DAYS_THRESHOLD) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 명의이전일자가 부과일자의 {}일 이내가 아님 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetRecord.getChgYmd(), levyCrtrYmd, daysBetween);
|
||||
return null;
|
||||
}
|
||||
log.info("[내사종결-명의이전 상품용-미필] 명의이전일자가 부과일자의 {}일 이내 확인 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetRecord.getChgYmd(), levyCrtrYmd, daysBetween);
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[내사종결-명의이전 상품용-미필] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD -1일) ==========
|
||||
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
|
||||
String targetChgYmdMinus1 = targetDate.minusDays(1).format(DATE_FORMATTER);
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {} (원본: {})", vin, targetChgYmdMinus1, targetChgYmd);
|
||||
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmdMinus1);
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[내사종결-명의이전 상품용-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmdMinus1);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 소유자명 비교 - 상품용 포함 여부 확인 ==========
|
||||
if (step4OwnerName == null || !step4OwnerName.contains("상품용")) {
|
||||
log.debug("[내사종결-명의이전 상품용-미필] 소유자명에 '상품용' 미포함 - Step4 소유자명: {}", step4OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildProductCloseLevyRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[내사종결-명의이전 상품용-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[내사종결-명의이전 상품용-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_04_INVESTIGATION_CLOSED;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[내사종결-명의이전 상품용-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[내사종결-명의이전 상품용-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 5. 날짜 수정 후 부과 검증 - 명의이전 이전소유자 상품용, 31일 초과 (미필)
|
||||
*
|
||||
* <p>명의이전 이전 소유자가 상품용이고, 명의이전일자가 부과일자의 31일 초과인 경우</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProductLevyOver31OmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public ProductLevyOver31OmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전 상품용-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 조건 1: 소유자명에 "상품용" 포함 여부 확인
|
||||
if (step1OwnerName == null || step1OwnerName.contains("상품용")) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전 상품용-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[날짜수정후부과-명의이전 상품용-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 조건에 맞는 레코드 찾기 ==========
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 갑부 상세 레코드 검색 시작 - 부과일자: {}, 검사유효기간 종료일: {}", levyCrtrYmd, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate latestChgDate = null;
|
||||
LocalDate levyDate = DateUtil.parseDate(levyCrtrYmd);
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "11" (명의이전)
|
||||
if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: CHG_YMD <= 부과일자
|
||||
if (chgDate.isAfter(levyDate)) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] CHG_YMD > 부과일자 - 변경일자: {}, 부과일자: {}", chgYmd, levyCrtrYmd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 가장 마지막 일자 찾기
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
latestChgDate = chgDate;
|
||||
targetRecord = record;
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 조건: 가장 마지막 명의이전일자가 부과일자의 기준일수 초과인지 확인
|
||||
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(latestChgDate, levyDate);
|
||||
if (daysBetween <= DAYS_THRESHOLD) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 명의이전일자가 부과일자의 {}일 이내임 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetRecord.getChgYmd(), levyCrtrYmd, daysBetween);
|
||||
return null;
|
||||
}
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 명의이전일자가 부과일자의 {}일 초과 확인 - 변경일자: {}, 부과일자: {}, 일수차이: {}일",
|
||||
DAYS_THRESHOLD, targetRecord.getChgYmd(), levyCrtrYmd, daysBetween);
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[날짜수정후부과-미필] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD -1일) ==========
|
||||
LocalDate targetDate = DateUtil.parseDate(targetChgYmd);
|
||||
String targetChgYmdMinus1 = targetDate.minusDays(1).format(DATE_FORMATTER);
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {} (원본: {})", vin, targetChgYmdMinus1, targetChgYmd);
|
||||
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmdMinus1);
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[날짜수정후부과-명의이전 상품용-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmdMinus1);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 소유자명 비교 - 상품용 포함 여부 확인 ==========
|
||||
if (step4OwnerName == null || !step4OwnerName.contains("상품용")) {
|
||||
log.debug("[날짜수정후부과-명의이전 상품용-미필] 소유자명에 '상품용' 미포함 - Step4 소유자명: {}", step4OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 명의이전 시점 소유자명에 '상품용' 확인! - 소유자명: {}", step4OwnerName);
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildProductCloseLevyRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
vhclno, levyCrtrYmd, inspVldPrdStart, inspVldPrdEnd, daysBetween
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[날짜수정후부과-명의이전 상품용-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[날짜수정후부과-명의이전 상품용-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_05_DATE_MODIFIED_LEVY;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[날짜수정후부과-명의이전 상품용-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[날짜수정후부과-명의이전 상품용-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,229 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 2. 상품용-변경등록 체크 (미필)
|
||||
*
|
||||
* <p>api-1번호출.소유자명.contains("상품용")</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProductUseOmChangeChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public ProductUseOmChangeChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[상품용-변경등록-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-변경등록-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
String step1RprsvOwnrIdecno = step1Record.getRprsvOwnrIdecno(); // 부과일자 기준 대표소유자 회원번호
|
||||
|
||||
log.info("[상품용-변경등록-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 조건 1: 소유자명에 "상품용" 포함 여부 확인
|
||||
if (step1OwnerName == null || !step1OwnerName.contains("상품용")) {
|
||||
log.debug("[상품용-변경등록-미필] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[상품용-변경등록-미필] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[상품용-변경등록-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-변경등록-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[상품용-변경등록-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[상품용-변경등록-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[상품용-변경등록-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[상품용-변경등록-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 조건에 맞는 레코드 찾기 ==========
|
||||
log.info("[상품용-변경등록-미필] 갑부 상세 레코드 검색 시작 - 부과일자: {}, 검사유효기간 종료일: {}", levyCrtrYmd, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate latestChgDate = null;
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "21" (변경등록)
|
||||
if (!"21".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: CHG_YMD <= 검사유효기간 종료일
|
||||
LocalDate inspVldPrdEndDate = DateUtil.parseDate(inspVldPrdEnd);
|
||||
if (chgDate.isAfter(inspVldPrdEndDate)) {
|
||||
log.debug("[상품용-변경등록-미필] CHG_YMD > 검사유효기간 종료일 - 변경일자: {}, 검사유효기간 종료일: {}", chgYmd, inspVldPrdEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
String spcablMttr = record.getSpcablMttr();
|
||||
if (!spcablMttr.contains("성명")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 가장 마지막 일자 찾기
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
latestChgDate = chgDate;
|
||||
targetRecord = record;
|
||||
log.debug("[상품용-변경등록-미필] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[상품용-변경등록-미필] 조건에 맞는 변경등록 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[상품용-변경등록-미필] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
|
||||
log.info("[상품용-변경등록-미필] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd);
|
||||
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmd.replace("-", ""));
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-변경등록-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
String step4RprsvOwnrIdecno = step4Record.getRprsvOwnrIdecno(); // 대표소유자 회원번호
|
||||
|
||||
log.info("[상품용-변경등록-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 소유자 회원번호 비교 ==========
|
||||
if (step4OwnerName == null || !step4RprsvOwnrIdecno.equals(step1RprsvOwnrIdecno)) {
|
||||
log.debug("[상품용-변경등록-미필] 소유자 불일치 - Step1 소유자: {}, Step4 소유자: {}", step1RprsvOwnrIdecno, step4RprsvOwnrIdecno);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[상품용-변경등록-미필] 소유자 일치 확인! - 소유자명: {}", step1OwnerName);
|
||||
log.info("[상품용-변경등록-미필] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildProductUseChangeRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
inspVldPrdEnd, levyCrtrYmd
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[상품용-변경등록-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[상품용-변경등록-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[상품용-변경등록-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[상품용-변경등록-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.request.NewLedgerRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.model.response.NewLedgerResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 1. 상품용 체크 (미필)
|
||||
*
|
||||
* <p>api-1번호출.소유자명.contains("상품용")</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProductUseOmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public ProductUseOmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
String levyCrtrYmd = existingData.getLevyCrtrYmd(); // 미필: 검사유효기간 종료일 + 145일
|
||||
String inspVldPrd = existingData.getInspVldPrd(); // 검사유효기간
|
||||
|
||||
// 검사유효기간에서 시작일과 종료일 추출
|
||||
String inspVldPrdStart = null;
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdStart = dates[0].trim().replace("-", "");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Step 1: 자동차기본정보 조회 (차량번호, 부과일자=검사유효기간 종료일+145일) ==========
|
||||
log.info("[상품용-미필] Step 1: 자동차기본정보 조회 - 차량번호: {}, 부과일자: {}", vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest step1Request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse step1Response = apiService.getBasicInfo(step1Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step1Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step1Response == null || step1Response.getRecord() == null || step1Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-미필] Step 1 응답 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step1Record = step1Response.getRecord().get(0);
|
||||
String vin = step1Record.getVin(); // 차대번호
|
||||
String step1OwnerName = step1Record.getRprsOwnrNm(); // 부과일자 기준 소유자명
|
||||
String step1RprsvOwnrIdecno = step1Record.getRprsvOwnrIdecno(); // 부과일자 기준 대표소유자 회원번호
|
||||
|
||||
log.info("[상품용-미필] Step 1 결과 - 차대번호: {}, 소유자명: {}", vin, step1OwnerName);
|
||||
|
||||
// 조건 1: 소유자명에 "상품용" 포함 여부 확인
|
||||
if (step1OwnerName == null || !step1OwnerName.contains("상품용")) {
|
||||
log.debug("[상품용-미필] 소유자명에 '상품용' 미포함 - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[상품용-미필] 소유자명에 '상품용' 포함 확인! - 차량번호: {}, 소유자명: {}", vhclno, step1OwnerName);
|
||||
|
||||
// ========== Step 2: 자동차기본정보 조회 (차대번호, 부과일자=오늘일자) ==========
|
||||
String today = LocalDate.now().format(DATE_FORMATTER);
|
||||
log.info("[상품용-미필] Step 2: 자동차기본정보 조회 - 차대번호: {}, 오늘일자: {}", vin, today);
|
||||
|
||||
NewBasicRequest step2Request = createBasicRequest(null, vin, today);
|
||||
NewBasicResponse step2Response = apiService.getBasicInfo(step2Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step2Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step2Response == null || step2Response.getRecord() == null || step2Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-미필] Step 2 응답 없음 - 차대번호: {}", vin);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step2Record = step2Response.getRecord().get(0);
|
||||
String currentVhclno = step2Record.getVhrno();
|
||||
String currentOwnerName = step2Record.getRprsOwnrNm();
|
||||
String currentIdecno = step2Record.getRprsvOwnrIdecno();
|
||||
String currentLegalDongCode = step2Record.getUsgsrhldStdgCd();
|
||||
|
||||
log.info("[상품용-미필] Step 2 결과 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
// ========== Step 3: 자동차등록원부(갑) 조회 ==========
|
||||
log.info("[상품용-미필] Step 3: 자동차등록원부(갑) 조회 - 차량번호: {}, 성명: {}, 주민번호: {}, 법정동코드: {}",
|
||||
currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
|
||||
NewLedgerRequest step3Request = createLedgerRequest(currentVhclno, currentOwnerName, currentIdecno, currentLegalDongCode);
|
||||
NewLedgerResponse step3Response = apiService.getLedgerInfo(step3Request);
|
||||
ledgerLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step3Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step3Response == null) {
|
||||
log.warn("[상품용-미필] Step 3 응답 없음 - 차량번호: {}", currentVhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NewLedgerResponse.Record> ledgerRecords = step3Response.getRecord();
|
||||
if (ledgerRecords == null || ledgerRecords.isEmpty()) {
|
||||
log.debug("[상품용-미필] 갑부 상세 내역 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 갑부 상세에서 조건에 맞는 레코드 찾기 ==========
|
||||
log.info("[상품용-미필] 갑부 상세 레코드 검색 시작 - 부과일자: {}, 검사유효기간 종료일: {}", levyCrtrYmd, inspVldPrdEnd);
|
||||
|
||||
NewLedgerResponse.Record targetRecord = null;
|
||||
LocalDate latestChgDate = null;
|
||||
|
||||
for (NewLedgerResponse.Record record : ledgerRecords) {
|
||||
String chgYmd = record.getChgYmd();
|
||||
String chgTaskSeCd = record.getChgTaskSeCd();
|
||||
|
||||
// 조건: CHG_TASK_SE_CD == "11" (명의이전)
|
||||
if (!"11".equals(chgTaskSeCd) || chgYmd == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate chgDate = DateUtil.parseDate(chgYmd);
|
||||
if (chgDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 조건: CHG_YMD <= 검사유효기간 종료일
|
||||
LocalDate inspVldPrdEndDate = DateUtil.parseDate(inspVldPrdEnd);
|
||||
if (chgDate.isAfter(inspVldPrdEndDate)) {
|
||||
log.debug("[상품용-미필] CHG_YMD > 검사유효기간 종료일 - 변경일자: {}, 검사유효기간 종료일: {}", chgYmd, inspVldPrdEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 가장 마지막 일자 찾기
|
||||
if (latestChgDate == null || chgDate.isAfter(latestChgDate)) {
|
||||
latestChgDate = chgDate;
|
||||
targetRecord = record;
|
||||
log.debug("[상품용-미필] 조건 충족 레코드 발견 - 변경일자: {}, 변경업무: {}", chgYmd, chgTaskSeCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRecord == null) {
|
||||
log.debug("[상품용-미필] 조건에 맞는 명의이전 레코드 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
String targetChgYmd = targetRecord.getChgYmd();
|
||||
log.info("[상품용-미필] 조건 충족 레코드 선택! 변경일자: {}, 변경업무: {}", targetChgYmd, targetRecord.getChgTaskSeNm());
|
||||
|
||||
// ========== Step 4: 자동차기본정보 조회 (차대번호, 부과일자=CHG_YMD) ==========
|
||||
log.info("[상품용-미필] Step 4: 자동차기본정보 조회 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd);
|
||||
|
||||
NewBasicRequest step4Request = createBasicRequest(null, vin, targetChgYmd.replace("-", ""));
|
||||
NewBasicResponse step4Response = apiService.getBasicInfo(step4Request);
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(step4Response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (step4Response == null || step4Response.getRecord() == null || step4Response.getRecord().isEmpty()) {
|
||||
log.warn("[상품용-미필] Step 4 응답 없음 - 차대번호: {}, 부과일자: {}", vin, targetChgYmd);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record step4Record = step4Response.getRecord().get(0);
|
||||
String step4OwnerName = step4Record.getRprsOwnrNm(); // CHG_YMD 시점의 소유자명
|
||||
String step4RprsvOwnrIdecno = step4Record.getRprsvOwnrIdecno(); // 대표소유자 회원번호
|
||||
|
||||
log.info("[상품용-미필] Step 4 결과 - 소유자명: {}", step4OwnerName);
|
||||
|
||||
// ========== 소유자 회원번호 비교 ==========
|
||||
if (step4OwnerName == null || !step4RprsvOwnrIdecno.equals(step1RprsvOwnrIdecno)) {
|
||||
log.debug("[상품용-미필] 소유자 불일치 - Step1 소유자: {}, Step4 소유자: {}", step1RprsvOwnrIdecno, step4RprsvOwnrIdecno);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[상품용-미필] 소유자 일치 확인! - 소유자명: {}", step1OwnerName);
|
||||
log.info("[상품용-미필] 모든 조건 충족! 차량번호: {}, 변경일자: {}", vhclno, targetChgYmd);
|
||||
|
||||
// ========== 비고 생성 ==========
|
||||
String rmrk = ComparisonOmRemarkBuilder.buildProductUseRemark(
|
||||
step1Record, step4Record, targetRecord,
|
||||
inspVldPrdEnd, levyCrtrYmd
|
||||
);
|
||||
|
||||
// ========== DB 업데이트 ==========
|
||||
existingData.setCarBassMatterInqireId(step1Response.getGeneratedId());
|
||||
existingData.setCarLedgerFrmbkId(step3Response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(step4OwnerName);
|
||||
existingData.setCarRegFrmbkChgTaskSeCd(targetRecord.getChgTaskSeCd());
|
||||
existingData.setCarRegFrmbkChgTaskSeNm(targetRecord.getChgTaskSeNm());
|
||||
existingData.setCarRegFrmbkChgYmd(targetRecord.getChgYmd().replace("-", ""));
|
||||
existingData.setCarRegFrmbkDtl(ComparisonOmRemarkBuilder.buildLedgerRecordDetail(targetRecord));
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[상품용-미필] 업데이트 실패: %s", vhclno));
|
||||
}
|
||||
|
||||
log.info("[상품용-미필] 처리 완료! 차량번호: {}", vhclno);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_02_PRODUCT_USE;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[상품용-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[상품용-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
package go.kr.project.carInspectionPenalty.registrationOm.service.impl.om_checker;
|
||||
|
||||
import egovframework.constant.TaskPrcsSttsConstants;
|
||||
import egovframework.exception.MessageException;
|
||||
import egovframework.util.DateUtil;
|
||||
import egovframework.util.SessionUtil;
|
||||
import go.kr.project.api.model.request.NewBasicRequest;
|
||||
import go.kr.project.api.model.response.NewBasicResponse;
|
||||
import go.kr.project.api.service.ExternalVehicleApiService;
|
||||
import go.kr.project.api.service.VmisCarBassMatterInqireLogService;
|
||||
import go.kr.project.api.service.VmisCarLedgerFrmbkLogService;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.model.CarFfnlgTrgtIncmpVO;
|
||||
import go.kr.project.carInspectionPenalty.registrationOm.service.impl.ComparisonOmRemarkBuilder;
|
||||
import go.kr.project.login.model.LoginUserVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 7. 이첩 검증 (미필)
|
||||
*
|
||||
* <p>DAYCNT 기반 부과기준일 계산 및 법정동코드 비교</p>
|
||||
* <p>미필의 경우 부과일자 = 검사유효기간 종료일 + 145일</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TransferOmChecker extends AbstractComparisonOmChecker {
|
||||
|
||||
public TransferOmChecker(CarFfnlgTrgtIncmpMapper carFfnlgTrgtIncmpMapper,
|
||||
ExternalVehicleApiService apiService,
|
||||
VmisCarBassMatterInqireLogService bassMatterLogService,
|
||||
VmisCarLedgerFrmbkLogService ledgerLogService) {
|
||||
super(carFfnlgTrgtIncmpMapper, apiService, bassMatterLogService, ledgerLogService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String check(CarFfnlgTrgtIncmpVO existingData) {
|
||||
String vhclno = existingData.getVhclno();
|
||||
|
||||
try {
|
||||
// TODO : DAYCNT 는 어떻게 가져옴?
|
||||
// DAYCNT 가져오기
|
||||
//String daycntStr = existingData.getDaycnt();
|
||||
String daycntStr = "111";
|
||||
if (daycntStr == null || daycntStr.isEmpty()) {
|
||||
log.debug("[이첩-미필] DAYCNT 없음 - 차량번호: {}", vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
int daycnt = Integer.parseInt(daycntStr);
|
||||
log.info("[이첩-미필] DAYCNT: {} - 차량번호: {}", daycnt, vhclno);
|
||||
|
||||
// 부과기준일 계산
|
||||
String levyCrtrYmd;
|
||||
String transferType;
|
||||
String inspVldPrd = existingData.getInspVldPrd();
|
||||
|
||||
// 검사유효기간에서 종료일 추출
|
||||
String inspVldPrdEnd = null;
|
||||
if (inspVldPrd != null && inspVldPrd.contains("~")) {
|
||||
String[] dates = inspVldPrd.split("~");
|
||||
inspVldPrdEnd = dates.length > 1 ? dates[1].trim().replace("-", "") : null;
|
||||
}
|
||||
|
||||
if (daycnt > 115) {
|
||||
// 이첩-2: 부과기준일 = 검사유효기간 종료일 + 115일
|
||||
LocalDate inspVldPrdEndDate = DateUtil.parseDate(inspVldPrdEnd);
|
||||
LocalDate levyCrtrDate = inspVldPrdEndDate.plusDays(115);
|
||||
levyCrtrYmd = levyCrtrDate.format(DATE_FORMATTER);
|
||||
transferType = "이첩-2";
|
||||
log.info("[이첩-2-미필] 부과기준일 = 검사유효기간 종료일({}) + 115일 = {}", inspVldPrdEnd, levyCrtrYmd);
|
||||
} else {
|
||||
// 이첩-1: 부과기준일 = 부과일자 (검사유효기간 종료일 + 145일)
|
||||
levyCrtrYmd = existingData.getLevyCrtrYmd();
|
||||
transferType = "이첩-1";
|
||||
log.info("[이첩-1-미필] 부과기준일 = 부과일자 = {}", levyCrtrYmd);
|
||||
}
|
||||
|
||||
// 자동차기본정보 API 호출 (부과기준일 기준)
|
||||
log.info("[{}-미필] 자동차기본정보 조회 - 차량번호: {}, 부과기준일: {}", transferType, vhclno, levyCrtrYmd);
|
||||
|
||||
NewBasicRequest request = createBasicRequest(vhclno, null, levyCrtrYmd);
|
||||
NewBasicResponse response = apiService.getBasicInfo(request);
|
||||
|
||||
// API 응답에 CAR_FFNLG_TRGT_INCMP_ID 업데이트
|
||||
bassMatterLogService.updateCarFfnlgTrgtIdByTxIdNewTx(response, existingData.getCarFfnlgTrgtIncmpId());
|
||||
|
||||
if (response == null || response.getRecord() == null || response.getRecord().isEmpty()) {
|
||||
log.warn("[{}-미필] 응답 없음 - 차량번호: {}", transferType, vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
NewBasicResponse.Record record = response.getRecord().get(0);
|
||||
String usgsrhldStdgCd = record.getUsgsrhldStdgCd(); // 사용본거지법정동코드
|
||||
|
||||
log.info("[{}-미필] API 응답 - 사용본거지법정동코드: {}", transferType, usgsrhldStdgCd);
|
||||
|
||||
// 법정동코드 유효성 검사
|
||||
if (usgsrhldStdgCd == null || usgsrhldStdgCd.length() < 4) {
|
||||
log.debug("[{}-미필] 법정동코드 없음 - 차량번호: {}", transferType, vhclno);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 세션에서 사용자 정보 조회
|
||||
LoginUserVO userInfo = SessionUtil.getLoginUser();
|
||||
if (userInfo == null || userInfo.getOrgCd() == null) {
|
||||
log.debug("[{}-미필] 사용자 정보 없음", transferType);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 법정동코드 앞 4자리 vs 사용자 조직코드 앞 4자리 비교
|
||||
String legalDong4 = usgsrhldStdgCd.substring(0, 4);
|
||||
String userOrgCd = userInfo.getOrgCd();
|
||||
String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd;
|
||||
|
||||
if (legalDong4.equals(userOrg4)) {
|
||||
log.debug("[{}-미필] 법정동코드 일치 - 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
transferType, vhclno, legalDong4, userOrg4);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.info("[{}-미필] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
|
||||
transferType, vhclno, legalDong4, userOrg4);
|
||||
|
||||
// 시군구 코드 및 시군구명 조회
|
||||
String sggCd = usgsrhldStdgCd.length() >= 5 ? usgsrhldStdgCd.substring(0, 5) : usgsrhldStdgCd;
|
||||
String sggNm = carFfnlgTrgtIncmpMapper.selectSggNmBySggCd(sggCd);
|
||||
if (sggNm == null || sggNm.isEmpty()) {
|
||||
log.warn("[{}-미필] 시군구명 조회 실패 - 시군구코드: {}", transferType, sggCd);
|
||||
sggNm = "";
|
||||
}
|
||||
|
||||
// 비고 생성
|
||||
String rmrk;
|
||||
if ("이첩-1".equals(transferType)) { // 5번
|
||||
rmrk = ComparisonOmRemarkBuilder.buildTransferCase1Remark(sggNm, userOrg4);
|
||||
} else { // 7번
|
||||
rmrk = ComparisonOmRemarkBuilder.buildTransferCase2Remark(sggNm, legalDong4);
|
||||
}
|
||||
|
||||
// DB 업데이트
|
||||
existingData.setCarBassMatterInqireId(response.getGeneratedId());
|
||||
existingData.setTaskPrcsSttsCd(TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER);
|
||||
existingData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
|
||||
existingData.setCarBscMttrInqFlnm(existingData.getOwnrNm());
|
||||
existingData.setCarBscMttrInqSggCd(sggCd);
|
||||
existingData.setCarBscMttrInqSggNm(sggNm);
|
||||
existingData.setRmrk(rmrk);
|
||||
|
||||
int updateCount = carFfnlgTrgtIncmpMapper.update(existingData);
|
||||
if (updateCount == 0) {
|
||||
throw new MessageException(String.format("[%s-미필] 업데이트 실패: %s", transferType, vhclno));
|
||||
}
|
||||
|
||||
log.info("[{}-미필] 처리 완료! 차량번호: {}, 시군구: {}({})", transferType, vhclno, sggNm, sggCd);
|
||||
return TaskPrcsSttsConstants.TASK_PRCS_STTS_CD_03_TRANSFER;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[이첩-미필] 검증 중 오류 발생 - 차량번호: {}", vhclno, e);
|
||||
throw new MessageException(String.format("[이첩-미필] 검증 중 오류 발생 - 차량번호: %s", vhclno), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,309 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="go.kr.project.carInspectionPenalty.registrationOm.mapper.CarFfnlgTrgtIncmpMapper">
|
||||
|
||||
<!-- 공통 검색 조건 -->
|
||||
<sql id="searchCondition">
|
||||
<if test='schRcptYmdStart != null and schRcptYmdStart != ""'>
|
||||
AND t.RCPT_YMD >= #{schRcptYmdStart}
|
||||
</if>
|
||||
<if test='schRcptYmdEnd != null and schRcptYmdEnd != ""'>
|
||||
AND t.RCPT_YMD <= #{schRcptYmdEnd}
|
||||
</if>
|
||||
<if test='schVhclno != null and schVhclno != ""'>
|
||||
AND t.VHCLNO LIKE CONCAT('%', #{schVhclno}, '%')
|
||||
</if>
|
||||
<if test='schOwnrNm != null and schOwnrNm != ""'>
|
||||
AND t.OWNR_NM LIKE CONCAT('%', #{schOwnrNm}, '%')
|
||||
</if>
|
||||
<if test='schTaskPrcsSttsCd != null and schTaskPrcsSttsCd.size() > 0'>
|
||||
AND t.TASK_PRCS_STTS_CD IN
|
||||
<foreach collection="schTaskPrcsSttsCd" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test='schPrcsYmdStart != null and schPrcsYmdStart != ""'>
|
||||
AND t.PRCS_YMD >= #{schPrcsYmdStart}
|
||||
</if>
|
||||
<if test='schPrcsYmdEnd != null and schPrcsYmdEnd != ""'>
|
||||
AND t.PRCS_YMD <= #{schPrcsYmdEnd}
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<!-- 과태료 대상 미필 목록 총 개수 조회 -->
|
||||
<select id="selectListTotalCount" parameterType="CarFfnlgTrgtIncmpVO" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM tb_car_ffnlg_trgt_incmp 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"/>
|
||||
</select>
|
||||
|
||||
<!-- 과태료 대상 미필 목록 조회 -->
|
||||
<select id="selectList" parameterType="CarFfnlgTrgtIncmpVO" resultType="CarFfnlgTrgtIncmpVO">
|
||||
SELECT
|
||||
t.CAR_FFNLG_TRGT_INCMP_ID AS carFfnlgTrgtIncmpId,
|
||||
t.RCPT_YMD AS rcptYmd,
|
||||
t.PRGRM_ID AS prgrmId,
|
||||
t.PRCS_YMD AS prcsYmd,
|
||||
t.OTPT_DT AS otptDt,
|
||||
t.NO AS no,
|
||||
t.VHCLNO AS vhclno,
|
||||
t.OWNR_NM AS ownrNm,
|
||||
ECL_DECRYPT(t.RRNO) AS rrno,
|
||||
t.CAR_NM AS carNm,
|
||||
t.USE_STRHLD_ADDR AS useStrhldAddr,
|
||||
t.INSP_VLD_PRD AS inspVldPrd,
|
||||
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
|
||||
t.TASK_PRCS_YMD AS taskPrcsYmd,
|
||||
t.RMRK AS rmrk,
|
||||
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
|
||||
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
|
||||
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
|
||||
t.CAR_BSC_MTTR_INQ_SGG_CD AS carBscMttrInqSggCd,
|
||||
t.CAR_BSC_MTTR_INQ_SGG_NM AS carBscMttrInqSggNm,
|
||||
t.CAR_REG_FRMBK_CHG_TASK_SE_CD AS carRegFrmbkChgTaskSeCd,
|
||||
t.CAR_REG_FRMBK_CHG_TASK_SE_NM AS carRegFrmbkChgTaskSeNm,
|
||||
t.CAR_REG_FRMBK_CHG_YMD AS carRegFrmbkChgYmd,
|
||||
t.CAR_REG_FRMBK_DTL AS carRegFrmbkDtl,
|
||||
t.REG_DT AS regDt,
|
||||
t.RGTR AS rgtr,
|
||||
t.DEL_YN AS delYn,
|
||||
t.DEL_DT AS delDt,
|
||||
t.DLTR AS dltr,
|
||||
cd.CD_NM AS taskPrcsSttsCdNm,
|
||||
u.USER_NM AS rgtrNm
|
||||
FROM tb_car_ffnlg_trgt_incmp 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.CAR_FFNLG_TRGT_INCMP_ID ASC
|
||||
<if test='pagingYn == "Y"'>
|
||||
limit #{startIndex}, #{perPage} /* 서버사이드 페이징 처리 */
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 과태료 대상 미필 상세 조회 -->
|
||||
<select id="selectOne" parameterType="CarFfnlgTrgtIncmpVO" resultType="CarFfnlgTrgtIncmpVO">
|
||||
SELECT
|
||||
t.CAR_FFNLG_TRGT_INCMP_ID AS carFfnlgTrgtIncmpId,
|
||||
t.RCPT_YMD AS rcptYmd,
|
||||
t.PRGRM_ID AS prgrmId,
|
||||
t.PRCS_YMD AS prcsYmd,
|
||||
t.OTPT_DT AS otptDt,
|
||||
t.NO AS no,
|
||||
t.VHCLNO AS vhclno,
|
||||
t.OWNR_NM AS ownrNm,
|
||||
ECL_DECRYPT(t.RRNO) AS rrno,
|
||||
t.CAR_NM AS carNm,
|
||||
t.USE_STRHLD_ADDR AS useStrhldAddr,
|
||||
t.INSP_VLD_PRD AS inspVldPrd,
|
||||
t.TASK_PRCS_STTS_CD AS taskPrcsSttsCd,
|
||||
t.TASK_PRCS_YMD AS taskPrcsYmd,
|
||||
t.RMRK AS rmrk,
|
||||
t.CAR_BASS_MATTER_INQIRE_ID AS carBassMatterInqireId,
|
||||
t.CAR_LEDGER_FRMBK_ID AS carLedgerFrmbkId,
|
||||
t.CAR_BSC_MTTR_INQ_FLNM AS carBscMttrInqFlnm,
|
||||
t.CAR_BSC_MTTR_INQ_SGG_CD AS carBscMttrInqSggCd,
|
||||
t.CAR_BSC_MTTR_INQ_SGG_NM AS carBscMttrInqSggNm,
|
||||
t.CAR_REG_FRMBK_CHG_TASK_SE_CD AS carRegFrmbkChgTaskSeCd,
|
||||
t.CAR_REG_FRMBK_CHG_TASK_SE_NM AS carRegFrmbkChgTaskSeNm,
|
||||
t.CAR_REG_FRMBK_CHG_YMD AS carRegFrmbkChgYmd,
|
||||
t.CAR_REG_FRMBK_DTL AS carRegFrmbkDtl,
|
||||
t.REG_DT AS regDt,
|
||||
t.RGTR AS rgtr,
|
||||
t.DEL_YN AS delYn,
|
||||
t.DEL_DT AS delDt,
|
||||
t.DLTR AS dltr,
|
||||
cd.CD_NM AS taskPrcsSttsCdNm,
|
||||
u.USER_NM AS rgtrNm
|
||||
FROM tb_car_ffnlg_trgt_incmp 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.CAR_FFNLG_TRGT_INCMP_ID = #{carFfnlgTrgtIncmpId}
|
||||
AND t.DEL_DT IS NULL
|
||||
</select>
|
||||
|
||||
<!-- 과태료 대상 미필 목록 엑셀 다운로드용 조회 -->
|
||||
<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.PRGRM_ID AS prgrmId,
|
||||
t.PRCS_YMD AS prcsYmd,
|
||||
t.NO AS no,
|
||||
t.VHCLNO AS vhclno,
|
||||
t.OWNR_NM AS ownrNm,
|
||||
ECL_DECRYPT(t.RRNO) AS rrno,
|
||||
t.CAR_NM AS carNm,
|
||||
t.USE_STRHLD_ADDR AS useStrhldAddr,
|
||||
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.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_incmp 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.CAR_FFNLG_TRGT_INCMP_ID ASC
|
||||
</select>
|
||||
|
||||
<!-- 과태료 대상 미필 등록 -->
|
||||
<insert id="insert" parameterType="CarFfnlgTrgtIncmpVO">
|
||||
INSERT INTO tb_car_ffnlg_trgt_incmp (
|
||||
CAR_FFNLG_TRGT_INCMP_ID,
|
||||
RCPT_YMD,
|
||||
PRGRM_ID,
|
||||
PRCS_YMD,
|
||||
OTPT_DT,
|
||||
NO,
|
||||
VHCLNO,
|
||||
OWNR_NM,
|
||||
RRNO,
|
||||
CAR_NM,
|
||||
USE_STRHLD_ADDR,
|
||||
INSP_VLD_PRD,
|
||||
TASK_PRCS_STTS_CD,
|
||||
TASK_PRCS_YMD,
|
||||
RMRK,
|
||||
CAR_BASS_MATTER_INQIRE_ID,
|
||||
CAR_LEDGER_FRMBK_ID,
|
||||
CAR_BSC_MTTR_INQ_FLNM,
|
||||
CAR_BSC_MTTR_INQ_SGG_CD,
|
||||
CAR_BSC_MTTR_INQ_SGG_NM,
|
||||
CAR_REG_FRMBK_CHG_TASK_SE_CD,
|
||||
CAR_REG_FRMBK_CHG_TASK_SE_NM,
|
||||
CAR_REG_FRMBK_CHG_YMD,
|
||||
CAR_REG_FRMBK_DTL,
|
||||
REG_DT,
|
||||
RGTR,
|
||||
DEL_YN
|
||||
) VALUES (
|
||||
CONCAT('CFI', LPAD(NEXTVAL(seq_car_ffnlg_trgt_incmp_id), 17, '0')),
|
||||
#{rcptYmd},
|
||||
#{prgrmId},
|
||||
#{prcsYmd},
|
||||
#{otptDt},
|
||||
#{no},
|
||||
#{vhclno},
|
||||
#{ownrNm},
|
||||
ECL_ENCRYPT(#{rrno}),
|
||||
#{carNm},
|
||||
#{useStrhldAddr},
|
||||
#{inspVldPrd},
|
||||
#{taskPrcsSttsCd},
|
||||
#{taskPrcsYmd},
|
||||
#{rmrk},
|
||||
#{carBassMatterInqireId},
|
||||
#{carLedgerFrmbkId},
|
||||
#{carBscMttrInqFlnm},
|
||||
#{carBscMttrInqSggCd},
|
||||
#{carBscMttrInqSggNm},
|
||||
#{carRegFrmbkChgTaskSeCd},
|
||||
#{carRegFrmbkChgTaskSeNm},
|
||||
#{carRegFrmbkChgYmd},
|
||||
#{carRegFrmbkDtl},
|
||||
NOW(),
|
||||
#{rgtr},
|
||||
'N'
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 과태료 대상 미필 수정 -->
|
||||
<update id="update" parameterType="CarFfnlgTrgtIncmpVO">
|
||||
UPDATE tb_car_ffnlg_trgt_incmp
|
||||
SET TASK_PRCS_STTS_CD = #{taskPrcsSttsCd},
|
||||
TASK_PRCS_YMD = #{taskPrcsYmd},
|
||||
RMRK = #{rmrk},
|
||||
CAR_BASS_MATTER_INQIRE_ID = #{carBassMatterInqireId},
|
||||
CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId},
|
||||
CAR_BSC_MTTR_INQ_FLNM = #{carBscMttrInqFlnm},
|
||||
CAR_BSC_MTTR_INQ_SGG_CD = #{carBscMttrInqSggCd},
|
||||
CAR_BSC_MTTR_INQ_SGG_NM = #{carBscMttrInqSggNm},
|
||||
CAR_REG_FRMBK_CHG_TASK_SE_CD = #{carRegFrmbkChgTaskSeCd},
|
||||
CAR_REG_FRMBK_CHG_TASK_SE_NM = #{carRegFrmbkChgTaskSeNm},
|
||||
CAR_REG_FRMBK_CHG_YMD = #{carRegFrmbkChgYmd},
|
||||
CAR_REG_FRMBK_DTL = #{carRegFrmbkDtl}
|
||||
WHERE CAR_FFNLG_TRGT_INCMP_ID = #{carFfnlgTrgtIncmpId}
|
||||
AND DEL_DT IS NULL
|
||||
</update>
|
||||
|
||||
<!-- 과태료 대상 미필의 처리상태와 비고만 수정 -->
|
||||
<update id="updateTaskPrcsSttsCdAndRmrk" parameterType="CarFfnlgTrgtIncmpVO">
|
||||
UPDATE tb_car_ffnlg_trgt_incmp
|
||||
SET TASK_PRCS_STTS_CD = #{taskPrcsSttsCd},
|
||||
TASK_PRCS_YMD = #{taskPrcsYmd},
|
||||
RMRK = #{rmrk}
|
||||
WHERE CAR_FFNLG_TRGT_INCMP_ID = #{carFfnlgTrgtIncmpId}
|
||||
AND DEL_DT IS NULL
|
||||
</update>
|
||||
|
||||
<!-- 과태료 대상 미필 삭제 (논리삭제) -->
|
||||
<update id="delete" parameterType="CarFfnlgTrgtIncmpVO">
|
||||
UPDATE tb_car_ffnlg_trgt_incmp
|
||||
SET DEL_DT = NOW(),
|
||||
DLTR = #{dltr}
|
||||
WHERE CAR_FFNLG_TRGT_INCMP_ID = #{carFfnlgTrgtIncmpId}
|
||||
AND DEL_DT IS NULL
|
||||
</update>
|
||||
|
||||
<!-- 차량번호와 검사유효기간 중복 체크 -->
|
||||
<select id="checkDuplicateVhclno" parameterType="CarFfnlgTrgtIncmpVO" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM tb_car_ffnlg_trgt_incmp
|
||||
WHERE VHCLNO = #{vhclno}
|
||||
AND INSP_VLD_PRD = #{inspVldPrd}
|
||||
AND DEL_DT IS NULL
|
||||
</select>
|
||||
|
||||
<!-- 시군구 코드로 시군구명 조회 -->
|
||||
<select id="selectSggNmBySggCd" parameterType="String" resultType="String">
|
||||
SELECT SGG_NM
|
||||
FROM tb_sgg_cd
|
||||
WHERE SGG_CD = #{sggCd}
|
||||
AND DEL_YN = 'N'
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 미필 부과일자 가산일 조회 (OM_DAY_CD 코드의 D값) -->
|
||||
<select id="selectOmDayPlusDay" resultType="String">
|
||||
SELECT CD_NM AS plusDay
|
||||
FROM tb_cd_detail
|
||||
WHERE CD_GROUP_ID = 'OM_DAY_CD'
|
||||
AND CD_ID = 'D'
|
||||
AND USE_YN = 'Y'
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,901 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
|
||||
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
|
||||
<%@ taglib prefix="dateUtil" uri="http://egovframework.go.kr/functions/date-util" %>
|
||||
|
||||
<!-- Main body -->
|
||||
<div class="main_body">
|
||||
<section id="section8" class="main_bars">
|
||||
<div class="bgs-main">
|
||||
<section id="section5">
|
||||
<div class="sub_title"></div>
|
||||
<button type="button" id="registerBtn" class="newbtn bg1">PRN 등록</button>
|
||||
<button type="button" id="downloadBtn" class="newbtn bg3 iconz">
|
||||
<span class="mdi mdi-file-document"></span>PRN 다운로드
|
||||
</button>
|
||||
<button type="button" id="excelDownloadBtn" class="newbtn bg3 iconz">
|
||||
<span class="mdi mdi-microsoft-excel"></span>엑셀 다운로드
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<div class="contants_body">
|
||||
<div class="gs_b_top">
|
||||
<ul class="lef">
|
||||
<li class="th">접수일자</li>
|
||||
<li>
|
||||
<input type="text" id="schRcptYmdStart" name="schRcptYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}"/> ~
|
||||
<input type="text" id="schRcptYmdEnd" name="schRcptYmdEnd" class="input calender datepicker" style="width: 120px;" autocomplete="off" value="${dateUtil:getCurrentDateTime('yyyy-MM-dd')}"/>
|
||||
</li>
|
||||
<li class="th">처리일자</li>
|
||||
<li>
|
||||
<input type="text" id="schPrcsYmdStart" name="schPrcsYmdStart" class="input calender datepicker" style="width: 120px;" autocomplete="off" value=""/> ~
|
||||
<input type="text" id="schPrcsYmdEnd" name="schPrcsYmdEnd" class="input calender datepicker" style="width: 120px;" autocomplete="off" value=""/>
|
||||
</li>
|
||||
<li class="th">차량번호</li>
|
||||
<li>
|
||||
<input type="text" id="schVhclno" name="schVhclno" class="input" style="width: 150px;" maxlength="30" autocomplete="off" placeholder="예: 12가3456"/>
|
||||
</li>
|
||||
<li class="th">소유자명</li>
|
||||
<li>
|
||||
<input type="text" id="schOwnrNm" name="schOwnrNm" class="input" style="width: 150px;" maxlength="75" autocomplete="off"/>
|
||||
</li>
|
||||
<li class="th">처리상태</li>
|
||||
<li>
|
||||
<c:forEach var="code" items="${taskPrcsSttsCdList}">
|
||||
<label style="margin-right: 10px; cursor: pointer;">
|
||||
<input type="checkbox" name="schTaskPrcsSttsCd" value="${code.cdId}" class="schTaskPrcsSttsCdCheckbox"/>
|
||||
${code.cdNm}
|
||||
</label>
|
||||
</c:forEach>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="rig2">
|
||||
<li><button type="button" id="search_btn" class="newbtnss bg1">검색</button></li>
|
||||
<li><button type="button" id="reset_btn" class="newbtnss bg5" style="margin-left: 5px;">초기화</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="gs_booking">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="box_column">
|
||||
<ul class="box_title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<li class="tit">미필 과태료 대상 목록</li>
|
||||
<li class="rig">
|
||||
<button type="button" id="callApiAllBtn" class="newbtn bg2-1">검색조건 전체 API 호출</button>
|
||||
<button type="button" id="callApiBtn" class="newbtn bg2-1">선택 API 호출</button>
|
||||
<button type="button" id="deleteBtn" class="newbtn bg6">선택 삭제</button>
|
||||
<button type="button" id="btn_cancel" class="newbtn bg6">수정 취소</button>
|
||||
<button type="button" id="btn_save" class="newbtn bg4">저장</button>
|
||||
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
|
||||
<select id="perPageSelect" class="input" style="width: 112px; ">
|
||||
<option value="15">페이지당 15</option>
|
||||
<option value="50">페이지당 50</option>
|
||||
<option value="100">페이지당 100</option>
|
||||
</select>
|
||||
<span class="page_number"><span id="currentPage"></span><span class="bar">/</span><span id="totalPages"></span> Pages</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="containers">
|
||||
<div id="grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Main body -->
|
||||
|
||||
<!-- 비고 레이어 팝업 -->
|
||||
<div id="rmrkLayerPopup" style="display: none; position: fixed; z-index: 10000; background: white; border: 2px solid #2196F3; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); min-width: 400px; max-width: 600px;">
|
||||
<div id="rmrkPopupHeader" style="background: #2196F3; color: white; padding: 12px 15px; cursor: move; border-radius: 6px 6px 0 0; display: flex; justify-content: space-between; align-items: center;">
|
||||
<span style="font-weight: bold;">비고 상세</span>
|
||||
<span id="rmrkPopupClose" onclick="closeRmrkPopup()" style="cursor: pointer; font-size: 20px; line-height: 1;">×</span>
|
||||
</div>
|
||||
<div style="padding: 20px; max-height: 400px; overflow-y: auto;">
|
||||
<pre id="rmrkPopupContent" style="white-space: pre-wrap; word-wrap: break-word; margin: 0; font-family: inherit; font-size: 14px; line-height: 1.6;"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 관리 모듈
|
||||
*/
|
||||
(function(window, $) {
|
||||
'use strict';
|
||||
|
||||
var SEARCH_COND = {};
|
||||
var LAST_GRID_SEARCH_COND = {};
|
||||
var GRID_PAGINATION_INFO = {
|
||||
totalCount: 0,
|
||||
page: 0,
|
||||
perPage: 0
|
||||
};
|
||||
|
||||
// 검색정보 설정
|
||||
var setSearchCond = function() {
|
||||
var schRcptYmdStart = $.trim(nvl($("#schRcptYmdStart").val(), ""));
|
||||
var schRcptYmdEnd = $.trim(nvl($("#schRcptYmdEnd").val(), ""));
|
||||
var schPrcsYmdStart = $.trim(nvl($("#schPrcsYmdStart").val(), ""));
|
||||
var schPrcsYmdEnd = $.trim(nvl($("#schPrcsYmdEnd").val(), ""));
|
||||
var schVhclno = $.trim(nvl($("#schVhclno").val(), ""));
|
||||
var schOwnrNm = $.trim(nvl($("#schOwnrNm").val(), ""));
|
||||
|
||||
var schTaskPrcsSttsCd = [];
|
||||
$("input[name='schTaskPrcsSttsCd']:checked").each(function() {
|
||||
schTaskPrcsSttsCd.push($(this).val());
|
||||
});
|
||||
|
||||
SEARCH_COND.schRcptYmdStart = schRcptYmdStart.replace(/-/g, '');
|
||||
SEARCH_COND.schRcptYmdEnd = schRcptYmdEnd.replace(/-/g, '');
|
||||
SEARCH_COND.schPrcsYmdStart = schPrcsYmdStart.replace(/-/g, '');
|
||||
SEARCH_COND.schPrcsYmdEnd = schPrcsYmdEnd.replace(/-/g, '');
|
||||
SEARCH_COND.schVhclno = schVhclno;
|
||||
SEARCH_COND.schOwnrNm = schOwnrNm;
|
||||
SEARCH_COND.schTaskPrcsSttsCd = schTaskPrcsSttsCd;
|
||||
};
|
||||
|
||||
// 다운로드 URL 생성
|
||||
var buildDownloadUrl = function() {
|
||||
setSearchCond();
|
||||
var baseUrl = '<c:url value="/carInspectionPenalty/registration-om/download.do"/>';
|
||||
var params = [];
|
||||
if (SEARCH_COND.schRcptYmdStart) params.push('schRcptYmdStart=' + encodeURIComponent(SEARCH_COND.schRcptYmdStart));
|
||||
if (SEARCH_COND.schRcptYmdEnd) params.push('schRcptYmdEnd=' + encodeURIComponent(SEARCH_COND.schRcptYmdEnd));
|
||||
if (SEARCH_COND.schPrcsYmdStart) params.push('schPrcsYmdStart=' + encodeURIComponent(SEARCH_COND.schPrcsYmdStart));
|
||||
if (SEARCH_COND.schPrcsYmdEnd) params.push('schPrcsYmdEnd=' + encodeURIComponent(SEARCH_COND.schPrcsYmdEnd));
|
||||
if (SEARCH_COND.schVhclno) params.push('schVhclno=' + encodeURIComponent(SEARCH_COND.schVhclno));
|
||||
if (SEARCH_COND.schOwnrNm) params.push('schOwnrNm=' + encodeURIComponent(SEARCH_COND.schOwnrNm));
|
||||
if (SEARCH_COND.schTaskPrcsSttsCd && SEARCH_COND.schTaskPrcsSttsCd.length > 0) {
|
||||
SEARCH_COND.schTaskPrcsSttsCd.forEach(function(val) {
|
||||
params.push('schTaskPrcsSttsCd=' + encodeURIComponent(val));
|
||||
});
|
||||
}
|
||||
return baseUrl + (params.length ? ('?' + params.join('&')) : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* 미필 과태료 대상 목록 관리 네임스페이스
|
||||
*/
|
||||
var CarFfnlgTrgtIncmpList = {
|
||||
selectedRow: null,
|
||||
|
||||
grid: {
|
||||
instance: null,
|
||||
|
||||
initConfig: function() {
|
||||
var dataSource = this.createDataSource();
|
||||
var perPage = parseInt($('#perPageSelect').val() || 15, 10);
|
||||
var gridConfig = new XitTuiGridConfig();
|
||||
|
||||
gridConfig.setOptDataSource(dataSource);
|
||||
gridConfig.setOptGridId('grid');
|
||||
gridConfig.setOptGridHeight(470);
|
||||
gridConfig.setOptRowHeight(30);
|
||||
gridConfig.setOptRowHeaderType('checkbox');
|
||||
gridConfig.setOptUseClientSort(false);
|
||||
gridConfig.setOptPageOptions({
|
||||
useClient: false,
|
||||
perPage: perPage
|
||||
});
|
||||
gridConfig.setOptColumnOptions({
|
||||
frozenCount: 5,
|
||||
frozenBorderWidth: 2,
|
||||
resizable: true
|
||||
});
|
||||
gridConfig.setOptColumns(this.getGridColumns());
|
||||
|
||||
return gridConfig;
|
||||
},
|
||||
|
||||
getGridColumns: function() {
|
||||
return [
|
||||
{
|
||||
header: '번호',
|
||||
name: '_rowNum',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
sortable: false,
|
||||
formatter: function(e) {
|
||||
var totalCount = GRID_PAGINATION_INFO.totalCount;
|
||||
var page = GRID_PAGINATION_INFO.page;
|
||||
var perPage = GRID_PAGINATION_INFO.perPage;
|
||||
var rowIndex = e.row.rowKey;
|
||||
return totalCount - (page - 1) * perPage - rowIndex;
|
||||
}
|
||||
},
|
||||
{ header: '접수일자', name: 'rcptYmd', align: 'center', width: 100,
|
||||
formatter: function(e) {
|
||||
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
|
||||
}
|
||||
},
|
||||
{ header: '프로그램ID', name: 'prgrmId', align: 'center', width: 100 },
|
||||
{ header: '처리일자', name: 'prcsYmd', align: 'center', width: 200 },
|
||||
{ header: '차량번호', name: 'vhclno', align: 'center', width: 100 },
|
||||
{ header: '소유자명', name: 'ownrNm', align: 'center', width: 100 },
|
||||
{ header: '주민등록번호', name: 'rrno', align: 'center', width: 130 },
|
||||
{ header: '자동차명', name: 'carNm', align: 'left', width: 150 },
|
||||
{ header: '사용본거지주소', name: 'useStrhldAddr', align: 'left', width: 250 },
|
||||
{ header: '검사유효기간', name: 'inspVldPrd', align: 'center', width: 200 },
|
||||
{ header: '업무처리일자', name: 'taskPrcsYmd', align: 'center', width: 100,
|
||||
formatter: function(e) {
|
||||
return e.value ? moment(e.value, 'YYYYMMDD').format('YYYY-MM-DD') : '';
|
||||
}
|
||||
},
|
||||
{
|
||||
header: '처리상태',
|
||||
name: 'taskPrcsSttsCd',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
editor: {
|
||||
type: 'select',
|
||||
options: {
|
||||
listItems: [
|
||||
<c:forEach var="code" items="${taskPrcsSttsCdList}" varStatus="status">
|
||||
{text: '${code.cdNm}', value: '${code.cdId}'}<c:if test="${!status.last}">,</c:if>
|
||||
</c:forEach>
|
||||
]
|
||||
}
|
||||
},
|
||||
formatter: function(props) {
|
||||
var value = props.value;
|
||||
var codeList = [
|
||||
<c:forEach var="code" items="${taskPrcsSttsCdList}" varStatus="status">
|
||||
{id: '${code.cdId}', nm: '${code.cdNm}'}<c:if test="${!status.last}">,</c:if>
|
||||
</c:forEach>
|
||||
];
|
||||
var code = codeList.find(function(c) { return c.id === value; });
|
||||
return code ? code.nm : value;
|
||||
}
|
||||
},
|
||||
{
|
||||
header: '비고',
|
||||
name: 'rmrk',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
editor: 'text',
|
||||
formatter: function(e) {
|
||||
var rmrk = e.value || '';
|
||||
var displayText = rmrk.length > 30 ? rmrk.substring(0, 30) + '...' : rmrk;
|
||||
if (rmrk && rmrk.trim()) {
|
||||
var escapedRmrk = rmrk.replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
|
||||
return '<span class="mdi mdi-note-text" style="color: #2196F3; cursor: pointer; margin-right: 5px;" onclick="showRmrkPopup(\'' + escapedRmrk + '\')"></span>' +
|
||||
'<span style="cursor: pointer;" onclick="showRmrkPopup(\'' + escapedRmrk + '\')">' + displayText + '</span>';
|
||||
}
|
||||
return displayText;
|
||||
}
|
||||
},
|
||||
{ header: '기본사항조회성명', name: 'carBscMttrInqFlnm', align: 'center', width: 100 },
|
||||
{ header: '기본사항조회시군구명', name: 'carBscMttrInqSggNm', align: 'center', width: 120 },
|
||||
{ header: '등록원부변경업무명', name: 'carRegFrmbkChgTaskSeNm', align: 'center', width: 120 },
|
||||
{ header: '등록원부변경일자', name: 'carRegFrmbkChgYmd', align: 'center', width: 120 },
|
||||
{ header: '등록원부상세', name: 'carRegFrmbkDtl', align: 'left', width: 250 },
|
||||
{ header: '미필과태료대상ID', name: 'carFfnlgTrgtIncmpId', align: 'center', width: 180 },
|
||||
{ header: '등록일시', name: 'regDt', align: 'center', width: 150 },
|
||||
{ header: '등록자', name: 'rgtrNm', align: 'center', width: 100 }
|
||||
];
|
||||
},
|
||||
|
||||
createDataSource: function() {
|
||||
return {
|
||||
api: {
|
||||
readData: {
|
||||
url: '<c:url value="/carInspectionPenalty/registration-om/list.ajax"/>',
|
||||
method: 'POST',
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
processData: true
|
||||
}
|
||||
},
|
||||
initialRequest: false,
|
||||
serializer: function(params) {
|
||||
setSearchCond();
|
||||
SEARCH_COND.perPage = params.perPage;
|
||||
SEARCH_COND.page = params.page;
|
||||
return $.param(SEARCH_COND);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
create: function() {
|
||||
var gridConfig = this.initConfig();
|
||||
var Grid = tui.Grid;
|
||||
this.instance = gridConfig.instance(Grid);
|
||||
Grid.applyTheme('striped');
|
||||
this.gridBindEvents();
|
||||
},
|
||||
|
||||
gridBindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
this.instance.on('successResponse', function(ev) {
|
||||
var responseObj = JSON.parse(ev.xhr.response);
|
||||
if(responseObj){
|
||||
$("#currentPage").text(responseObj.data.pagination.page);
|
||||
$("#totalPages").text(responseObj.data.pagination.totalPages);
|
||||
var totalCount = responseObj.data.pagination.totalCount;
|
||||
$("#totalCount").text('총 ' + totalCount.toLocaleString() + '건');
|
||||
|
||||
GRID_PAGINATION_INFO.totalCount = responseObj.data.pagination.totalCount;
|
||||
GRID_PAGINATION_INFO.page = responseObj.data.pagination.page;
|
||||
GRID_PAGINATION_INFO.perPage = responseObj.data.pagination.perPage;
|
||||
}
|
||||
CarFfnlgTrgtIncmpList.selectedRow = null;
|
||||
});
|
||||
|
||||
this.instance.on('check', function(ev) {
|
||||
var rowKey = ev.rowKey;
|
||||
CarFfnlgTrgtIncmpList.selectedRow = self.instance.getRow(rowKey);
|
||||
});
|
||||
|
||||
this.instance.on('uncheck', function(ev) {
|
||||
CarFfnlgTrgtIncmpList.selectedRow = null;
|
||||
});
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
setSearchCond();
|
||||
LAST_GRID_SEARCH_COND = Object.assign({}, SEARCH_COND);
|
||||
this.instance.readData(1);
|
||||
},
|
||||
|
||||
cancelChanges: function() {
|
||||
var modifiedRows = this.instance.getModifiedRows();
|
||||
if (modifiedRows.createdRows.length === 0 &&
|
||||
modifiedRows.updatedRows.length === 0 &&
|
||||
modifiedRows.deletedRows.length === 0) {
|
||||
alert('취소할 변경사항이 없습니다.');
|
||||
return;
|
||||
}
|
||||
if (confirm('모든 변경사항을 취소하시겠습니까?')) {
|
||||
var currentPage = this.instance.getPagination().getCurrentPage();
|
||||
this.instance.readData(currentPage);
|
||||
}
|
||||
},
|
||||
|
||||
saveData: function() {
|
||||
var self = this;
|
||||
var modifiedRows = this.instance.getModifiedRows();
|
||||
if (modifiedRows.createdRows.length === 0 &&
|
||||
modifiedRows.updatedRows.length === 0 &&
|
||||
modifiedRows.deletedRows.length === 0) {
|
||||
alert('저장할 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
if (confirm("변경된 내용을 저장하시겠습니까?")) {
|
||||
$.ajax({
|
||||
url: '<c:url value="/carInspectionPenalty/registration-om/saveAll.ajax"/>',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(modifiedRows),
|
||||
success: function(response) {
|
||||
if (response.result) {
|
||||
alert(response.message);
|
||||
self.instance.readData(1);
|
||||
} else {
|
||||
alert(response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("저장 실패:", error);
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
init: function() {
|
||||
this.grid.create();
|
||||
this.bindEvents();
|
||||
this.grid.reload();
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
var self = this;
|
||||
|
||||
$("#search_btn").on('click', function() {
|
||||
self.grid.reload();
|
||||
});
|
||||
|
||||
$("#reset_btn").on('click', function() {
|
||||
$("#schRcptYmdStart").val("${dateUtil:getCurrentDateAddDays('yyyy-MM-dd', -15)}");
|
||||
$("#schRcptYmdEnd").val("${dateUtil:getCurrentDateTime('yyyy-MM-dd')}");
|
||||
$("#schPrcsYmdStart").val("");
|
||||
$("#schPrcsYmdEnd").val("");
|
||||
$("#schVhclno").val("");
|
||||
$("#schOwnrNm").val("");
|
||||
$("input[name='schTaskPrcsSttsCd']").prop('checked', false);
|
||||
self.grid.reload();
|
||||
});
|
||||
|
||||
$("#registerBtn").on('click', function() {
|
||||
self.openUploadPopup();
|
||||
});
|
||||
|
||||
$("#deleteBtn").on('click', function() {
|
||||
self.deleteData();
|
||||
});
|
||||
|
||||
$("#callApiBtn").on('click', function() {
|
||||
self.callApiAndCompare();
|
||||
});
|
||||
|
||||
$("#callApiAllBtn").on('click', function() {
|
||||
self.callApiAndCompareAll();
|
||||
});
|
||||
|
||||
$("#downloadBtn").on('click', function() {
|
||||
var url = buildDownloadUrl();
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
$("#excelDownloadBtn").on('click', function() {
|
||||
var $form = $('<form>', {
|
||||
method: 'POST',
|
||||
action: '<c:url value="/carInspectionPenalty/registration-om/excel.do"/>'
|
||||
});
|
||||
setSearchCond();
|
||||
$.each(SEARCH_COND, function(key, value) {
|
||||
if (key === 'schTaskPrcsSttsCd') {
|
||||
if (value && value.length > 0) {
|
||||
value.forEach(function(val) {
|
||||
$form.append($('<input>', { type: 'hidden', name: key, value: val }));
|
||||
});
|
||||
}
|
||||
} else if (value) {
|
||||
$form.append($('<input>', { type: 'hidden', name: key, value: value }));
|
||||
}
|
||||
});
|
||||
$form.appendTo('body').submit().remove();
|
||||
});
|
||||
|
||||
$("#perPageSelect").on('change', function() {
|
||||
var perPage = parseInt($(this).val(), 10);
|
||||
self.grid.instance.setPerPage(perPage);
|
||||
self.grid.reload();
|
||||
});
|
||||
|
||||
$(".gs_b_top input").on('keypress', function(e) {
|
||||
if (e.which === 13) {
|
||||
self.grid.reload();
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_cancel').on('click', function() {
|
||||
self.grid.cancelChanges();
|
||||
});
|
||||
|
||||
$('#btn_save').on('click', function() {
|
||||
self.grid.saveData();
|
||||
});
|
||||
},
|
||||
|
||||
openUploadPopup: function() {
|
||||
var popupUrl = '<c:url value="/carInspectionPenalty/registration-om/uploadPopup.do"/>';
|
||||
var popup = openPopup(popupUrl, 800, 450, 'uploadPopup');
|
||||
var checkPopupClosed = setInterval(function() {
|
||||
if (popup && popup.closed) {
|
||||
clearInterval(checkPopupClosed);
|
||||
CarFfnlgTrgtIncmpList.grid.reload();
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
deleteData: function() {
|
||||
var checkedRows = this.grid.instance.getCheckedRows();
|
||||
if (checkedRows.length === 0) {
|
||||
alert("삭제할 데이터를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!confirm(checkedRows.length + "건의 데이터를 삭제하시겠습니까?")) {
|
||||
return;
|
||||
}
|
||||
var deleteIds = checkedRows.map(function(row) {
|
||||
return row.carFfnlgTrgtIncmpId;
|
||||
});
|
||||
$.ajax({
|
||||
url: '<c:url value="/carInspectionPenalty/registration-om/deleteBatch.ajax"/>',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(deleteIds),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert("삭제가 완료되었습니다.");
|
||||
CarFfnlgTrgtIncmpList.grid.reload();
|
||||
} else {
|
||||
alert("삭제 중 오류가 발생했습니다: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("삭제 실패:", error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
callApiAndCompare: function() {
|
||||
var checkedRows = this.grid.instance.getCheckedRows();
|
||||
if (checkedRows.length === 0) {
|
||||
alert("API 호출할 데이터를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
var nonRcptRows = checkedRows.filter(function(row) {
|
||||
return row.taskPrcsSttsCd !== '01';
|
||||
});
|
||||
if (nonRcptRows.length > 0) {
|
||||
alert("접수 상태(01)인 데이터만 API 호출이 가능합니다.\n접수 상태가 아닌 데이터가 " + nonRcptRows.length + "건 포함되어 있습니다.");
|
||||
return;
|
||||
}
|
||||
if (!confirm(checkedRows.length + "건의 데이터에 대해 API를 호출하고 비교하시겠습니까?\n(미필: 부과일자 = 검사유효기간 종료일 + 145일)")) {
|
||||
return;
|
||||
}
|
||||
var targetList = checkedRows.map(function(row) {
|
||||
return {
|
||||
carFfnlgTrgtIncmpId: row.carFfnlgTrgtIncmpId,
|
||||
vhclno: row.vhclno,
|
||||
inspVldPrd: row.inspVldPrd,
|
||||
ownrNm: row.ownrNm,
|
||||
carNm: row.carNm
|
||||
};
|
||||
});
|
||||
$.ajax({
|
||||
url: '<c:url value="/carInspectionPenalty/registration-om/compareWithApi.ajax"/>',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(targetList),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert("API 호출 및 비교가 완료되었습니다.\n\n" + response.message);
|
||||
CarFfnlgTrgtIncmpList.grid.reload();
|
||||
} else {
|
||||
alert("오류: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("API 호출 실패:", error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
callApiAndCompareAll: function() {
|
||||
if (!confirm("현재 검색조건의 전체 데이터에 대해 API를 호출하고 비교하시겠습니까?\n(미필: 부과일자 = 검사유효기간 종료일 + 145일)")) {
|
||||
return;
|
||||
}
|
||||
setSearchCond();
|
||||
var params = Object.assign({}, SEARCH_COND, { pagingYn: 'N' });
|
||||
$.ajax({
|
||||
url: '<c:url value="/carInspectionPenalty/registration-om/compareWithApiAll.ajax"/>',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(params),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert("API 호출 및 비교가 완료되었습니다.\n\n" + response.message);
|
||||
CarFfnlgTrgtIncmpList.grid.reload();
|
||||
} else {
|
||||
alert("오류: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("API 호출 실패:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
CarFfnlgTrgtIncmpList.init();
|
||||
});
|
||||
|
||||
// 전역에 노출
|
||||
window.CarFfnlgTrgtIncmpList = CarFfnlgTrgtIncmpList;
|
||||
|
||||
})(window, jQuery);
|
||||
|
||||
/**
|
||||
* 비고 레이어 팝업 열기 (전역 함수)
|
||||
*/
|
||||
function showRmrkPopup(rmrkText) {
|
||||
var $popup = $('#rmrkLayerPopup');
|
||||
var $content = $('#rmrkPopupContent');
|
||||
rmrkText = rmrkText.replace(/\\n/g, '\n').replace(/\\'/g, "'");
|
||||
$content.text(rmrkText);
|
||||
$popup.css({ display: 'block', transform: 'none' });
|
||||
var windowWidth = $(window).width();
|
||||
var windowHeight = $(window).height();
|
||||
var popupWidth = $popup.outerWidth();
|
||||
var popupHeight = $popup.outerHeight();
|
||||
$popup.css({
|
||||
left: (windowWidth - popupWidth) / 2 + 'px',
|
||||
top: (windowHeight - popupHeight) / 2 + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function closeRmrkPopup() {
|
||||
$('#rmrkLayerPopup').hide();
|
||||
}
|
||||
|
||||
$(document).on('click', function(e) {
|
||||
var $popup = $('#rmrkLayerPopup');
|
||||
if ($popup.is(':visible') && !$(e.target).closest('#rmrkLayerPopup, .mdi-note-text').length) {
|
||||
closeRmrkPopup();
|
||||
}
|
||||
});
|
||||
|
||||
// 엑셀 다운로드 버튼
|
||||
$('#btnExcel').click(function() {
|
||||
downloadExcel();
|
||||
});
|
||||
|
||||
// 선택 API 비교 버튼
|
||||
$('#btnApiCompare').click(function() {
|
||||
compareWithApi();
|
||||
});
|
||||
|
||||
// 전체 API 비교 버튼
|
||||
$('#btnApiCompareAll').click(function() {
|
||||
compareWithApiAll();
|
||||
});
|
||||
|
||||
// 저장 버튼
|
||||
$('#btnSave').click(function() {
|
||||
saveAll();
|
||||
});
|
||||
|
||||
// 선택 삭제 버튼
|
||||
$('#btnDelete').click(function() {
|
||||
deleteSelected();
|
||||
});
|
||||
}
|
||||
|
||||
function searchList() {
|
||||
var params = $('#searchForm').serializeArray();
|
||||
var paramObj = {};
|
||||
|
||||
$.each(params, function(i, field) {
|
||||
if (field.name === 'schTaskPrcsSttsCd') {
|
||||
if (!paramObj[field.name]) {
|
||||
paramObj[field.name] = [];
|
||||
}
|
||||
paramObj[field.name].push(field.value);
|
||||
} else {
|
||||
paramObj[field.name] = field.value;
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/list.ajax',
|
||||
type: 'POST',
|
||||
data: paramObj,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
grid.resetData(response.data);
|
||||
grid.refreshLayout();
|
||||
} else {
|
||||
alert('조회 실패: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('조회 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openUploadPopup() {
|
||||
var popup = window.open(
|
||||
contextPath + '/carInspectionPenalty/registration-om/uploadPopup.do',
|
||||
'uploadPopup',
|
||||
'width=500,height=300,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
|
||||
// 팝업 닫힘 감지 (업로드 완료 후 목록 새로고침)
|
||||
var pollTimer = setInterval(function() {
|
||||
if (popup.closed) {
|
||||
clearInterval(pollTimer);
|
||||
searchList();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function downloadPrn() {
|
||||
var params = $('#searchForm').serialize();
|
||||
window.location.href = contextPath + '/carInspectionPenalty/registration-om/download.do?' + params;
|
||||
}
|
||||
|
||||
function downloadExcel() {
|
||||
var params = $('#searchForm').serialize();
|
||||
|
||||
// form submit 방식으로 변경
|
||||
var form = $('<form>', {
|
||||
action: contextPath + '/carInspectionPenalty/registration-om/excel.do',
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
var searchParams = $('#searchForm').serializeArray();
|
||||
$.each(searchParams, function(i, field) {
|
||||
form.append($('<input>', {
|
||||
type: 'hidden',
|
||||
name: field.name,
|
||||
value: field.value
|
||||
}));
|
||||
});
|
||||
|
||||
form.appendTo('body').submit().remove();
|
||||
}
|
||||
|
||||
function compareWithApi() {
|
||||
var checkedRows = grid.getCheckedRows();
|
||||
|
||||
if (checkedRows.length === 0) {
|
||||
alert('API 비교할 데이터를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 접수(01) 상태인 것만 필터링
|
||||
var targetList = checkedRows.filter(function(row) {
|
||||
return row.taskPrcsSttsCd === '01';
|
||||
}).map(function(row) {
|
||||
return {
|
||||
carFfnlgTrgtIncmpId: row.carFfnlgTrgtIncmpId,
|
||||
vhclno: row.vhclno,
|
||||
inspVldPrd: row.inspVldPrd,
|
||||
ownrNm: row.ownrNm,
|
||||
carNm: row.carNm
|
||||
};
|
||||
});
|
||||
|
||||
if (targetList.length === 0) {
|
||||
alert('접수 상태(01)인 데이터만 API 비교가 가능합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(targetList.length + '건의 데이터에 대해 API 비교를 수행하시겠습니까?\n(미필: 부과일자 = 검사유효기간 종료일 + 145일)')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/compareWithApi.ajax',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(targetList),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(response.message);
|
||||
searchList();
|
||||
} else {
|
||||
alert('API 비교 실패: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('API 비교 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function compareWithApiAll() {
|
||||
if (!confirm('검색 조건에 해당하는 전체 데이터에 대해 API 비교를 수행하시겠습니까?\n(미필: 부과일자 = 검사유효기간 종료일 + 145일)')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var params = {};
|
||||
var searchParams = $('#searchForm').serializeArray();
|
||||
$.each(searchParams, function(i, field) {
|
||||
if (field.name === 'schTaskPrcsSttsCd') {
|
||||
if (!params[field.name]) {
|
||||
params[field.name] = [];
|
||||
}
|
||||
params[field.name].push(field.value);
|
||||
} else {
|
||||
params[field.name] = field.value;
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/compareWithApiAll.ajax',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(params),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(response.message);
|
||||
searchList();
|
||||
} else {
|
||||
alert('전체 API 비교 실패: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('전체 API 비교 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveAll() {
|
||||
var modifiedData = grid.getModifiedRows();
|
||||
|
||||
if (modifiedData.createdRows.length === 0 &&
|
||||
modifiedData.updatedRows.length === 0 &&
|
||||
modifiedData.deletedRows.length === 0) {
|
||||
alert('저장할 데이터가 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('수정된 데이터를 저장하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/saveAll.ajax',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(modifiedData),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(response.message);
|
||||
searchList();
|
||||
} else {
|
||||
alert('저장 실패: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
var checkedRows = grid.getCheckedRows();
|
||||
|
||||
if (checkedRows.length === 0) {
|
||||
alert('삭제할 데이터를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(checkedRows.length + '건의 데이터를 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var deleteIds = checkedRows.map(function(row) {
|
||||
return row.carFfnlgTrgtIncmpId;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/deleteBatch.ajax',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(deleteIds),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert(response.message);
|
||||
searchList();
|
||||
} else {
|
||||
alert('삭제 실패: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 날짜 포맷 함수
|
||||
function formatDate(value) {
|
||||
if (!value) return '';
|
||||
var str = String(value).replace(/[^0-9]/g, '');
|
||||
if (str.length === 8) {
|
||||
return str.substring(0, 4) + '-' + str.substring(4, 6) + '-' + str.substring(6, 8);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 주민등록번호 마스킹 함수
|
||||
function maskRrno(value) {
|
||||
if (!value) return '';
|
||||
var str = String(value).replace(/[^0-9]/g, '');
|
||||
if (str.length >= 6) {
|
||||
return str.substring(0, 6) + '-*******';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,163 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||
|
||||
<style>
|
||||
.upload-area {
|
||||
padding: 20px;
|
||||
}
|
||||
.upload-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.file-input-wrapper {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.file-info {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.btn-area {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.upload-result {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.upload-result.success {
|
||||
background: #dff0d8;
|
||||
color: #3c763d;
|
||||
}
|
||||
.upload-result.error {
|
||||
background: #f2dede;
|
||||
color: #a94442;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="upload-area">
|
||||
<h4><i class="fa fa-upload"></i> 미필 과태료 대상 PRN 파일 업로드</h4>
|
||||
<hr>
|
||||
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="file-input-wrapper">
|
||||
<label for="file">PRN 파일 선택</label>
|
||||
<input type="file" class="form-control" id="file" name="file" accept=".prn,.txt">
|
||||
<p class="help-block">* PRN 또는 TXT 파일만 업로드 가능합니다.</p>
|
||||
<p class="help-block">* 업로드 시 UTF-8 → EUC-KR 변환됩니다.</p>
|
||||
</div>
|
||||
|
||||
<div id="fileInfo" class="file-info" style="display: none;">
|
||||
<strong>선택된 파일:</strong> <span id="fileName"></span><br>
|
||||
<strong>파일 크기:</strong> <span id="fileSize"></span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="uploadResult" class="upload-result" style="display: none;"></div>
|
||||
|
||||
<div class="btn-area">
|
||||
<button type="button" class="btn btn-primary" id="btnUpload" disabled>
|
||||
<i class="fa fa-upload"></i> 업로드
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" id="btnClose">
|
||||
<i class="fa fa-times"></i> 닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var contextPath = '${pageContext.request.contextPath}';
|
||||
|
||||
$(document).ready(function() {
|
||||
// 파일 선택 이벤트
|
||||
$('#file').change(function() {
|
||||
var file = this.files[0];
|
||||
if (file) {
|
||||
$('#fileName').text(file.name);
|
||||
$('#fileSize').text(formatFileSize(file.size));
|
||||
$('#fileInfo').show();
|
||||
$('#btnUpload').prop('disabled', false);
|
||||
$('#uploadResult').hide();
|
||||
} else {
|
||||
$('#fileInfo').hide();
|
||||
$('#btnUpload').prop('disabled', true);
|
||||
}
|
||||
});
|
||||
|
||||
// 업로드 버튼 클릭
|
||||
$('#btnUpload').click(function() {
|
||||
uploadFile();
|
||||
});
|
||||
|
||||
// 닫기 버튼 클릭
|
||||
$('#btnClose').click(function() {
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
|
||||
function uploadFile() {
|
||||
var file = $('#file')[0].files[0];
|
||||
if (!file) {
|
||||
alert('파일을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 파일 확장자 검사
|
||||
var ext = file.name.split('.').pop().toLowerCase();
|
||||
if (ext !== 'prn' && ext !== 'txt') {
|
||||
alert('PRN 또는 TXT 파일만 업로드 가능합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
$('#btnUpload').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> 업로드 중...');
|
||||
|
||||
$.ajax({
|
||||
url: contextPath + '/carInspectionPenalty/registration-om/upload.ajax',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$('#uploadResult')
|
||||
.removeClass('error')
|
||||
.addClass('success')
|
||||
.html('<i class="fa fa-check"></i> ' + response.message.replace(/\n/g, '<br>'))
|
||||
.show();
|
||||
|
||||
// 성공 시 파일 입력 초기화
|
||||
$('#file').val('');
|
||||
$('#fileInfo').hide();
|
||||
} else {
|
||||
$('#uploadResult')
|
||||
.removeClass('success')
|
||||
.addClass('error')
|
||||
.html('<i class="fa fa-times"></i> ' + response.message.replace(/\n/g, '<br>'))
|
||||
.show();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$('#uploadResult')
|
||||
.removeClass('success')
|
||||
.addClass('error')
|
||||
.html('<i class="fa fa-times"></i> 업로드 중 오류가 발생했습니다.')
|
||||
.show();
|
||||
},
|
||||
complete: function() {
|
||||
$('#btnUpload').prop('disabled', false).html('<i class="fa fa-upload"></i> 업로드');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
var k = 1024;
|
||||
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
</script>
|
||||
Loading…
Reference in New Issue