재부과 신규로직 init

dev
박성영 3 months ago
parent a022fb939d
commit e5974bcca6

@ -7,7 +7,7 @@ create table tb_act_info
CRDN_NO varchar(6) null comment '단속 번호',
PSTN_INFO_ID varchar(10) not null comment '위치 정보 ID',
ACT_BGNG_YMD char(8) null comment '행위 시작 일자',
ACT_TYPE_CD varchar(2) null comment '행위 유형 코드',
ACT_TYPE_CD varchar(3) null comment '행위 유형 코드',
VLTN_LWRG_CD_1 varchar(3) null comment '위반 법규 코드 1',
VLTN_LWRG_CD_2 varchar(3) null comment '위반 법규 코드 2',
STRCT_IDX_CD varchar(3) null comment '구조 지수 코드',
@ -17,9 +17,6 @@ create table tb_act_info
VRTC decimal(10, 2) null comment '세로',
AREA decimal(10, 2) null comment '면적',
RMRK varchar(1000) null comment '비고',
ACTN_LAST_YMD varchar(8) null comment '조치 최종 일자',
ACTN_WHOL_AREA decimal(10, 2) null comment '조치 총 면적',
ACTN_PRCS_STTS_CD varchar(1) null comment '조치 처리 상태 코드',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',
@ -27,10 +24,10 @@ create table tb_act_info
DEL_YN char not null comment '삭제 여부',
DEL_DT datetime null comment '삭제 일시',
DLTR varchar(11) null comment '삭제자',
ACT_NO varchar(1) null comment '행위 번호',
ACTN_YMD varchar(8) null,
ACTN_AREA decimal(10, 2) null,
ACTN_RMRK varchar(1000) null
ACT_NO varchar(2) null comment '행위 번호',
ACTN_LAST_YMD varchar(8) null comment '조치 최종 일자',
ACTN_WHOL_AREA decimal(10, 2) null comment '조치 총 면적',
ACTN_PRCS_STTS_CD varchar(1) null comment '조치 처리 상태 코드'
)
comment '행위 정보';

@ -0,0 +1,16 @@
create table tb_cmpttn_rt
(
CMPTTN_RT_CD varchar(3) not null comment '산정 률 코드'
primary key,
VLTN_BDST varchar(100) null comment '위반 건축물',
LAWLPRVS varchar(100) null comment '법조문',
CMPTTN_RT decimal(3) null comment '산정 률',
VLTN_BDST_DTL varchar(1000) null comment '위반 건축물 상세',
USE_YN char not null comment '사용 여부',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',
MDFR varchar(11) null comment '수정자'
)
comment '산정 률';

@ -0,0 +1,14 @@
create table tb_cmpttn_rt_2
(
CMPTTN_RT_2_CD varchar(1) not null comment '산정 률 2 코드'
primary key,
VLTN_MTTR varchar(100) null comment '위반 사항',
CMPTTN_RT_2 decimal(3) null comment '산정 률 2',
USE_YN char not null comment '사용 여부',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',
MDFR varchar(11) null comment '수정자'
)
comment '산정 률 2';

@ -31,6 +31,8 @@ create table tb_crdn
DEL_YN char not null comment '삭제 여부',
DEL_DT datetime null comment '삭제 일시',
DLTR varchar(11) null comment '삭제자',
RELEVY_TRGT_CRDN_YR varchar(4) null comment '재부과 대상 단속 연도',
RELEVY_TRGT_CRDN_NO varchar(6) null comment '재부과 대상 단속 번호',
primary key (CRDN_YR, CRDN_NO)
)
comment '단속';

@ -9,7 +9,8 @@ create table tb_implt_trpr_info
IMPLT_TRPR_DADDR varchar(320) null comment '이행 대상자 상세주소',
IMPLT_TRPR_ZIP varchar(6) null comment '이행 대상자 우편번호',
IMPLT_TRPR_SE_CD varchar(1) null comment '이행 대상자 구분 코드',
OWNR_ACTR_INFO_ID varchar(1) null comment '소유자 행위자 정보 ID',
OWNR_ACTR_INFO_ID varchar(10) not null comment '소유자 행위자 정보 ID',
ACT_NO varchar(2) null comment '행위 번호',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',

@ -0,0 +1,21 @@
create table tb_res_ret
(
res_div int not null,
res_no varchar(20) not null,
res_name varchar(50) null,
addr_main varchar(100) null,
addr_sub varchar(100) null,
zip_code varchar(6) null,
tel_no varchar(50) null,
tel_no2 varchar(50) null,
mail_addr varchar(200) null,
del_yn int null,
createdate datetime null,
modifydate datetime null,
in_date datetime null,
up_date datetime null,
datain_user varchar(50) null,
dataup_user varchar(50) null
)
charset = euckr;

@ -0,0 +1,21 @@
create table tb_res_ret_backup
(
res_div int not null,
res_no varchar(20) not null,
res_name varchar(50) null,
addr_main varchar(100) null,
addr_sub varchar(100) null,
zip_code varchar(6) null,
tel_no varchar(50) null,
tel_no2 varchar(50) null,
mail_addr varchar(200) null,
del_yn int null,
createdate datetime null,
modifydate datetime null,
in_date datetime null,
up_date datetime null,
datain_user varchar(50) null,
dataup_user varchar(50) null
)
charset = euckr;

@ -4,16 +4,19 @@ create table tb_strct_idx
primary key,
STRCT_NM varchar(100) null comment '구조 명',
STRCT_IDX decimal(10, 2) null comment '구조 지수',
RDVLRT_CN_YR_CNT decimal(2) null comment '잔가율 내용 연도 수',
LAST_YR_RDVLRT decimal(4, 2) null comment '최종 연도 잔가율',
DPRT decimal(4, 4) null comment '감가상각률',
BSCS_CSTRN_Y_BDST_CMPTTN_RT decimal(10, 2) null comment '기초 공사 Y 건축물 산정 비율',
BSCS_CSTRN_N_BDST_CMPTTN_RT decimal(10, 2) null comment '기초 공사 N 건축물 산정 비율',
DUP_ETBLDG_BDST_CMPTTN_RT decimal(10, 2) null comment '복층 증축 건축물 산정 비율',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',
MDFR varchar(11) null comment '수정자',
DEL_YN char not null comment '삭제 여부',
DEL_DT datetime null comment '삭제 일시',
DLTR varchar(11) null comment '삭제자',
RDVLRT_CN_YR_CNT decimal(2) null comment '잔가율 내용 연도 수',
LAST_YR_RDVLRT decimal(4, 2) null comment '최종 연도 잔가율',
DPRT decimal(4, 4) null comment '감가상각률'
DLTR varchar(11) null comment '삭제자'
)
comment '구조 지수';

@ -637,4 +637,50 @@ public class FileUtil {
return filename.substring(lastDotIndex + 1).toLowerCase();
}
/**
* .
* .
*
* @param sourcePath
* @param targetPath
* @return
* @throws IOException
*/
public boolean copyFile(String sourcePath, String targetPath) throws IOException {
if (sourcePath == null || targetPath == null) {
throw new IOException("원본 또는 대상 파일 경로가 null입니다.");
}
File sourceFile = new File(sourcePath);
File targetFile = new File(targetPath);
// 원본 파일 존재 여부 확인
if (!sourceFile.exists()) {
throw new IOException("원본 파일이 존재하지 않습니다: " + sourcePath);
}
// 원본 파일이 일반 파일인지 확인
if (!sourceFile.isFile()) {
throw new IOException("원본이 일반 파일이 아닙니다: " + sourcePath);
}
// 대상 디렉토리 생성
File targetDir = targetFile.getParentFile();
if (targetDir != null && !targetDir.exists()) {
if (!targetDir.mkdirs()) {
throw new IOException("대상 디렉토리 생성 실패: " + targetDir.getAbsolutePath());
}
}
// 파일 복사
try {
Files.copy(sourceFile.toPath(), targetFile.toPath(),
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
throw new IOException("파일 복사 실패: " + sourcePath + " -> " + targetPath, e);
}
}
}

@ -12,6 +12,7 @@ import go.kr.project.crdn.crndRegistAndView.crdnActInfo.service.CrdnPhotoService
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -44,6 +45,12 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd
private final CrdnPhotoMapper mapper;
private final FileUtil fileUtil;
@Value("${file.upload.sub-dirs.crdn-act-photo}")
String atcFileStorePath;
@Value("${file.upload.sub-dirs.crdn-actn-photo}")
String atcnFileStorePath;
public List<CrdnPhotoVO> selectPhotoListByActInfoIdAndPhotoSeCd(CrdnPhotoVO photoVO) {
log.debug("단속 사진 목록 조회: actInfoId={}, CrdnPhotoSeCd={}", photoVO.getActInfoId(), photoVO.getCrdnPhotoSeCd());
return mapper.selectPhotoListByActInfoIdAndPhotoSeCd(photoVO);
@ -85,7 +92,7 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd
try {
// 중요한 로직 주석: FileUtil을 통해 파일 업로드 처리
List<FileVO> uploadedFiles = fileUtil.uploadFiles(files, "crdn-act-photo");
List<FileVO> uploadedFiles = fileUtil.uploadFiles(files, atcFileStorePath);
for (FileVO fileVO : uploadedFiles) {
// 중요한 로직 주석: 다음 사진 순번 생성
@ -151,7 +158,7 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd
}
// 중요한 로직 주석: 새 파일 업로드
List<FileVO> uploadedFiles = fileUtil.uploadFiles(Arrays.asList(file), "crdn-act-photo");
List<FileVO> uploadedFiles = fileUtil.uploadFiles(Arrays.asList(file), atcFileStorePath);
if (!uploadedFiles.isEmpty()) {
FileVO newFileVO = uploadedFiles.get(0);
@ -317,7 +324,7 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd
try {
// 중요한 로직 주석: FileUtil을 통한 파일 저장 (단일 파일을 리스트로 변환하여 처리)
List<MultipartFile> singleFileList = Arrays.asList(file);
List<FileVO> uploadedFiles = fileUtil.uploadFiles(singleFileList, "crdn-act-photo");
List<FileVO> uploadedFiles = fileUtil.uploadFiles(singleFileList, atcnFileStorePath);
if (uploadedFiles.isEmpty()) {
log.error("파일 저장 실패: {}", file.getOriginalFilename());

@ -0,0 +1,134 @@
package go.kr.project.crdn.crndRegistAndView.main.controller;
import egovframework.constant.MessageConstants;
import egovframework.constant.TilesConstants;
import egovframework.exception.MessageException;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRegistAndViewVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRelevyVO;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnRelevyService;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnRegistAndViewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
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.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.controller
* fileName : CrdnRelevyController
* author :
* date : 2025-09-29
* description :
* : -
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-29
*/
@Controller
@RequestMapping("/crdn/crndRegistAndView/crdnRelevy")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "재부과 관리", description = "재부과 관련 API")
public class CrdnRelevyController {
/** 재부과 서비스 */
private final CrdnRelevyService relevyService;
/** 단속 서비스 */
private final CrdnRegistAndViewService crdnService;
/**
* .
* @param model
* @return
*/
@GetMapping("/relevyPopup.do")
@Operation(summary = "재부과 팝업 화면", description = "재부과 등록을 위한 팝업 화면을 제공합니다.")
public String relevyPopup(
@Parameter(description = "단속 년도", required = true) @RequestParam String srcCrdnYr,
@Parameter(description = "단속 번호", required = true) @RequestParam String srcCrdnNo,
@Parameter(description = "팝업 모드", required = true) @RequestParam String mode,
Model model) {
log.debug("재부과 팝업 화면 요청");
model.addAttribute("srcCrdnYr", srcCrdnYr);
model.addAttribute("srcCrdnNo", srcCrdnNo);
model.addAttribute("mode", mode);
return "crdn/crndRegistAndView/main/crdnRelevy/relevyPopup" + TilesConstants.POPUP;
}
/**
* .
* @param crdnYr
* @param crdnNo
* @return
*/
@GetMapping("/selectOrgCrdnInfo.ajax")
@Operation(summary = "원본 단속 정보 조회", description = "재부과 대상이 되는 원본 단속 정보를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청")
})
public ResponseEntity<?> selectOrgCrdnInfo(
@Parameter(description = "단속 년도", required = true) @RequestParam String crdnYr,
@Parameter(description = "단속 번호", required = true) @RequestParam String crdnNo) {
log.debug("원본 단속 정보 조회 요청: crdnYr={}, crdnNo={}", crdnYr, crdnNo);
CrdnRelevyVO searchVO = new CrdnRelevyVO();
searchVO.setCrdnYr(crdnYr);
searchVO.setCrdnNo(crdnNo);
CrdnRelevyVO orgCrdnInfo = relevyService.selectCrdnPstnInfoOne(searchVO);
if (orgCrdnInfo == null) {
throw new MessageException("원본 단속 정보가 존재하지 않습니다.");
}
return ApiResponseUtil.success(orgCrdnInfo);
}
/**
* .
* @param relevyVO
* @return
*/
@PostMapping("/saveRelevy.ajax")
@Operation(summary = "재부과 저장", description = "재부과 정보를 저장하고 새로운 단속 건을 생성합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청")
})
public ResponseEntity<?> saveRelevy(@RequestBody CrdnRelevyVO relevyVO) {
log.debug("재부과 저장 요청: {}", relevyVO);
// 필수값 검증
if (relevyVO.getSrcCrdnYr() == null || relevyVO.getSrcCrdnNo() == null) {
throw new MessageException("원본 단속 정보가 없습니다.");
}
if (relevyVO.getRelevyRsn() == null || relevyVO.getRelevyRsn().trim().isEmpty()) {
throw new MessageException("재부과 사유를 입력해주세요.");
}
// 등록자 정보 설정
relevyVO.setRgtr(SessionUtil.getUserId());
// 재부과 처리 (원본 단속 정보 복사 및 새로운 단속 생성)
Map<String, Object> result = relevyService.processRelevy(relevyVO);
return ApiResponseUtil.success(result);
}
}

@ -0,0 +1,106 @@
package go.kr.project.crdn.crndRegistAndView.main.mapper;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRelevyVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnPhotoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.mapper
* fileName : CrdnRelevyMapper
* author :
* date : 2025-09-29
* description : MyBatis
* :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-29
*/
@Mapper
public interface CrdnRelevyMapper {
// 재부과 대상정보 조회
CrdnRelevyVO selectCrdnPstnInfoOne(CrdnRelevyVO crdnRelevyVO);
/**
* .
* @param crdnYr
* @return (10 LPAD )
*/
String selectNextCrdnNo(@Param("crdnYr") String crdnYr);
/**
* .
* , 'Y' .
* @param relevyVO
* @return
*/
int insertCrdnBasicInfo(CrdnRelevyVO relevyVO);
/**
* .
* @param relevyVO
* @return
*/
int insertCrdnPstnInfo(CrdnRelevyVO relevyVO);
/**
* .
* @param relevyVO
* @return
*/
int insertCrdnOwnrInfo(CrdnRelevyVO relevyVO);
/**
* .
* .
* @param relevyVO
* @return
*/
int insertCrdnActInfo(CrdnRelevyVO relevyVO);
/**
* .
* .
* @param relevyVO
* @return
*/
int insertCrdnPhotoInfo(CrdnRelevyVO relevyVO);
/**
* .
* .
* @param relevyVO
* @return
*/
int insertCrdnActrInfo(CrdnRelevyVO relevyVO);
/**
* .
* (CRDN_PRCS_STTS_CD_30_CRC_CMD)
* , .
* @param relevyVO
* @return
*/
int updateCrdnStatus(CrdnRelevyVO relevyVO);
/**
* .
* .
* @param srcCrdnYr
* @param srcCrdnNo
* @return
*/
List<CrdnPhotoVO> selectSrcPhotoList(@Param("srcCrdnYr") String srcCrdnYr, @Param("srcCrdnNo") String srcCrdnNo);
/**
* .
* .
* @param updateParams (newCrdnYr, newCrdnNo, crdnPhotoSn, crdnPhotoNm)
* @return
*/
int updateRelevyPhotoInfo(java.util.Map<String, Object> updateParams);
}

@ -0,0 +1,151 @@
package go.kr.project.crdn.crndRegistAndView.main.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;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.model
* fileName : CrdnRelevyVO
* author :
* date : 2025-09-29
* description : Value Object
* : VO - .
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-29
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CrdnRelevyVO extends PagingVO {
// ==================== 원본/신규 단속 정보 ====================
/** 단속 연도 */
private String crdnYr;
/** 단속 번호 */
private String crdnNo;
/** 원본 단속 연도 */
private String srcCrdnYr;
/** 원본 단속 번호 */
private String srcCrdnNo;
/** 신규 단속 연도 (재부과 후 생성되는 단속 연도) */
private String newCrdnYr;
/** 신규 단속 번호 (재부과 후 생성되는 단속 번호) */
private String newCrdnNo;
// ==================== 재부과 정보 ====================
/** 재부과 사유 */
private String relevyRsn;
// ==================== 단속 기본 정보 ====================
/** 시군구 코드 */
private String sggCd;
/** 시군구 코드 명 */
private String sggCdNm;
/** 지역 구분 코드 */
private String rgnSeCd;
/** 지역 구분 코드 명 */
private String rgnSeCdNm;
/** 적발 방법 코드 */
private String dsclMthdCd;
/** 적발 방법 코드 명 */
private String dsclMthdCdNm;
/** 적발 일자 */
private String dsclYmd;
/** 조사원 */
private String exmnr;
/** 비고 */
private String rmrk;
/** 최초 단속 연도 */
private String frstCrdnYr;
/** 최초 단속 번호 */
private String frstCrdnNo;
/** 재부과 여부 */
private String relevyYn;
/** 가중 부과 대상 여부 */
private String agrvtnLevyTrgtYn;
/** 단속 처리 상태 코드 */
private String crdnPrcsSttsCd;
/** 단속 처리 상태 코드 명 */
private String crdnPrcsSttsCdNm;
/** 단속 처리 일자 */
private String crdnPrcsYmd;
// ==================== 처리 기간 정보 ====================
/** 처분 사전 예고 시작일 */
private String dspsBfhdBgngYmd;
/** 처분 사전 예고 종료일 */
private String dspsBfhdEndYmd;
/** 시정명령 시작일 */
private String crcCmdBgngYmd;
/** 시정명령 종료일 */
private String crcCmdEndYmd;
/** 시정촉구 시작일 */
private String crcUrgBgngYmd;
/** 시정촉구 종료일 */
private String crcUrgEndYmd;
/** 부과 예고 시작일 */
private String levyPrvntcBgngYmd;
/** 부과 예고 종료일 */
private String levyPrvntcEndYmd;
/** 부과 시작일 */
private String levyBgngYmd;
/** 부과 종료일 */
private String levyEndYmd;
/** 납부 촉구 시작일 */
private String payUrgBgngYmd;
/** 납부 촉구 종료일 */
private String payUrgEndYmd;
// ==================== 위치/주소 정보 ====================
/** 소재지 행정동 코드 */
private String stdgEmdCd;
/** 소재지 행정동 코드 명 */
private String stdgEmdCdNm;
/** 지목 코드 */
private String ldcgCd;
/** 지목 코드 명 */
private String ldcgCdNm;
/** 지번 */
private String ptout;
/** 공시지가 */
private String oalp;
/** 지번 전체 주소 */
private String lotnoWholAddr;
/** 도로명 전체 주소 */
private String roadNmWholAddr;
/** 우편번호 */
private String zip;
/** 지상지하 여부 코드 */
private String udgdYnCd;
/** 산 여부 코드 */
private String mtnYnCd;
// ==================== 등록자 정보 ====================
/** 등록자 계정 */
private String rgtrAcnt;
/** 등록자 이름 */
private String rgtrNm;
// ==================== 공통 컬럼 ====================
/** 등록자 */
private String rgtr;
/** 등록 일시 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime regDt;
/** 수정자 */
private String mdfr;
/** 수정 일시 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime mdfcnDt;
}

@ -0,0 +1,34 @@
package go.kr.project.crdn.crndRegistAndView.main.service;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRelevyVO;
import java.util.Map;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.service
* fileName : CrdnRelevyService
* author :
* date : 2025-09-29
* description :
* :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-29
*/
public interface CrdnRelevyService {
/*
*
* */
CrdnRelevyVO selectCrdnPstnInfoOne(CrdnRelevyVO crdnRelevyVO);
/**
* .
* , .
*
* @param relevyVO VO
* @return ( )
*/
Map<String, Object> processRelevy(CrdnRelevyVO relevyVO);
}

@ -0,0 +1,233 @@
package go.kr.project.crdn.crndRegistAndView.main.service.impl;
import egovframework.exception.MessageException;
import egovframework.util.FileUtil;
import go.kr.project.crdn.crndRegistAndView.main.mapper.CrdnRegistAndViewMapper;
import go.kr.project.crdn.crndRegistAndView.main.mapper.CrdnRelevyMapper;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRelevyVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnPhotoVO;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnRegistAndViewService;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnRelevyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static egovframework.constant.SEQConstants.SEQ_CRDN;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.service
* fileName : CrdnRelevyServiceImpl
* author :
* date : 2025-09-29
* description :
* :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-29
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CrdnRelevyServiceImpl implements CrdnRelevyService {
private final CrdnRelevyMapper relevyMapper;
private final CrdnRegistAndViewMapper crdnRegistAndViewMapper;
private final CrdnRegistAndViewService crdnRegistAndViewService;
private final FileUtil fileUtil;
@Override
public CrdnRelevyVO selectCrdnPstnInfoOne(CrdnRelevyVO crdnRelevyVO) {
return relevyMapper.selectCrdnPstnInfoOne(crdnRelevyVO);
}
/**
* .
* .
*
* @param relevyVO VO
* @return ( )
*/
@Override
@Transactional
public Map<String, Object> processRelevy(CrdnRelevyVO relevyVO) {
log.info("재부과 처리 시작: srcCrdnYr={}, srcCrdnNo={}", relevyVO.getSrcCrdnYr(), relevyVO.getSrcCrdnNo());
try {
// 1. 현재 년도 설정
String currentYear = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy"));
// 년도별 시퀀스 존재 여부 확인 및 생성
ensureSequenceExists(currentYear);
// 2. 신규 단속 번호 생성 (현재 년도 기준)
relevyVO.setNewCrdnYr(currentYear);
String newCrdnNo = relevyMapper.selectNextCrdnNo(currentYear);
relevyVO.setNewCrdnNo(newCrdnNo);
// 3. 기본 단속 정보 복사 (tb_crdn)
int result = relevyMapper.insertCrdnBasicInfo(relevyVO);
if (result <= 0) {
throw new MessageException("기본 단속 정보 복사 실패");
}
// 4. 위치 정보 복사 (tb_crdn_pstn_info)
relevyMapper.insertCrdnPstnInfo(relevyVO);
// 5. 소유자 정보 복사 (tb_crdn_ownr_info)
relevyMapper.insertCrdnOwnrInfo(relevyVO);
// 6. 불법행위 정보 복사 (조치완료 제외)
// 6-1. 행위 정보 (tb_crdn_act_info)
relevyMapper.insertCrdnActInfo(relevyVO);
// 6-2. 첨부파일 복사 (조치완료 제외) - 데이터베이스 레코드 복사
relevyMapper.insertCrdnPhotoInfo(relevyVO);
// 6-3. 물리적 파일 복사
processPhysicalFileCopy(relevyVO);
// 7. 조치 정보 복사 (조치완료 제외)
// 7-1. 행위자 정보 (tb_crdn_actr_info)
relevyMapper.insertCrdnActrInfo(relevyVO);
// 8. 현재 진행 상태를 시정명령(30)으로 설정 및 최초단속 정보 업데이트
relevyMapper.updateCrdnStatus(relevyVO);
log.info("재부과 처리 완료: 신규 단속번호={}", newCrdnNo);
// 결과 반환
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("newCrdnYr", currentYear);
resultMap.put("newCrdnNo", newCrdnNo);
resultMap.put("message", "재부과가 성공적으로 처리되었습니다.");
return resultMap;
} catch (Exception e) {
log.error("재부과 처리 중 오류 발생", e);
throw new MessageException("재부과 처리 중 오류가 발생했습니다: " + e.getMessage());
}
}
/**
* .
* .
*
* @param relevyVO
* @throws MessageException
*/
private void processPhysicalFileCopy(CrdnRelevyVO relevyVO) throws MessageException {
log.info("물리적 파일 복사 시작: srcCrdnYr={}, srcCrdnNo={}", relevyVO.getSrcCrdnYr(), relevyVO.getSrcCrdnNo());
try {
// 원본 첨부파일 목록 조회
List<CrdnPhotoVO> srcPhotoList = relevyMapper.selectSrcPhotoList(relevyVO.getSrcCrdnYr(), relevyVO.getSrcCrdnNo());
if (srcPhotoList == null || srcPhotoList.isEmpty()) {
log.info("복사할 첨부파일이 없습니다.");
return;
}
log.info("복사할 첨부파일 개수: {}", srcPhotoList.size());
// 각 파일에 대해 복사 처리
for (CrdnPhotoVO srcPhoto : srcPhotoList) {
copyPhysicalFile(srcPhoto, relevyVO);
}
log.info("물리적 파일 복사 완료");
} catch (Exception e) {
log.error("물리적 파일 복사 중 오류 발생", e);
throw new MessageException("첨부파일 복사 중 오류가 발생했습니다: " + e.getMessage());
}
}
/**
* .
*
* @param srcPhoto
* @param relevyVO
*/
private void copyPhysicalFile(CrdnPhotoVO srcPhoto, CrdnRelevyVO relevyVO) {
try {
// 원본 파일 전체 경로 구성
String srcFilePath = srcPhoto.getCrdnPhotoPath() + "/" + srcPhoto.getCrdnPhotoNm();
// UUID를 이용한 새로운 저장 파일명 생성 (기존 로직 사용)
String fileExt = "";
String orglFileName = srcPhoto.getOrgnlPhotoNm();
if (orglFileName != null && orglFileName.contains(".")) {
fileExt = orglFileName.substring(orglFileName.lastIndexOf(".") + 1).toLowerCase();
}
String newStrgFileNm = UUID.randomUUID() + "." + fileExt;
// 새로운 파일 경로 구성 (동일한 디렉토리 구조 유지)
String newFilePath = srcPhoto.getCrdnPhotoPath(); // 동일한 경로 사용
String targetFilePath = newFilePath + "/" + newStrgFileNm;
// 물리적 파일 복사
boolean copySuccess = fileUtil.copyFile(srcFilePath, targetFilePath);
if (copySuccess) {
// 복사된 파일 정보로 데이터베이스 업데이트
// 재부과된 단속 정보의 첨부파일명을 새로운 UUID 파일명으로 변경
Map<String, Object> updateParams = new HashMap<>();
updateParams.put("newCrdnYr", relevyVO.getNewCrdnYr());
updateParams.put("newCrdnNo", relevyVO.getNewCrdnNo());
updateParams.put("crdnPhotoSn", srcPhoto.getCrdnPhotoSn());
updateParams.put("crdnPhotoNm", newStrgFileNm);
int updateResult = relevyMapper.updateRelevyPhotoInfo(updateParams);
if (updateResult <= 0) {
throw new MessageException("첨부파일 정보 업데이트 실패: " + srcPhoto.getOrgnlPhotoNm());
}
log.debug("파일 복사 성공: {} -> {}", srcFilePath, targetFilePath);
} else {
throw new MessageException("파일 복사 실패: " + srcPhoto.getOrgnlPhotoNm());
}
} catch (IOException e) {
log.error("파일 복사 중 IOException 발생: {}", srcPhoto.getOrgnlPhotoNm(), e);
throw new MessageException("파일 복사 실패: " + srcPhoto.getOrgnlPhotoNm() + " - " + e.getMessage());
} catch (Exception e) {
log.error("파일 복사 중 예외 발생: {}", srcPhoto.getOrgnlPhotoNm(), e);
throw new MessageException("파일 처리 중 오류 발생: " + srcPhoto.getOrgnlPhotoNm() + " - " + e.getMessage());
}
}
/**
* 퀀 .
* @param year (YYYY )
*/
private void ensureSequenceExists(String year) {
String sequenceName = SEQ_CRDN + year;
log.debug("년도별 시퀀스 확인 - 시퀀스명: {}", sequenceName);
try {
// 시퀀스 존재 여부 확인 (SHOW CREATE SEQUENCE 실행)
String sequenceDefinition = crdnRegistAndViewMapper.checkSequenceExists(sequenceName);
log.debug("년도별 시퀀스 이미 존재 - 시퀀스명: {}, 정의: {}", sequenceName, sequenceDefinition);
} catch (Exception e) {
// 시퀀스가 존재하지 않으면 예외 발생 -> 시퀀스 생성
log.info("년도별 시퀀스 생성 - 시퀀스명: {} (원인: {})", sequenceName, e.getMessage());
crdnRegistAndViewMapper.createSequence(sequenceName);
log.debug("년도별 시퀀스 생성 완료 - 시퀀스명: {}", sequenceName);
}
}
}

@ -177,7 +177,7 @@
LEFT JOIN tb_cd_detail rgn ON rgn.CD_GROUP_ID = 'RGN_SE_CD' AND rgn.CD_ID = c.RGN_SE_CD
LEFT JOIN tb_cd_detail dscl ON dscl.CD_GROUP_ID = 'DSCL_MTHD_CD' AND dscl.CD_ID = c.DSCL_MTHD_CD
LEFT JOIN tb_cd_detail stts ON stts.CD_GROUP_ID = 'CRDN_PRCS_STTS_CD' AND stts.CD_ID = c.CRDN_PRCS_STTS_CD
LEFT JOIN tb_user u ON u.USER_ID = c.RGTR AND u.USE_YN = 'Y'
LEFT JOIN tb_user u ON u.USER_ID = c.RGTR
WHERE c.DEL_YN = 'N'
AND c.CRDN_YR = #{crdnYr}
AND c.CRDN_NO = #{crdnNo}

@ -0,0 +1,408 @@
<?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.crdn.crndRegistAndView.main.mapper.CrdnRelevyMapper">
<!-- 단속-위치정보 단건 조회 -->
<select id="selectCrdnPstnInfoOne" parameterType="CrdnRelevyVO" resultType="CrdnRelevyVO">
/* CrdnRelevyMapper.selectCrdnPstnInfoOne : 단속-위치정보 단건 조회 */
SELECT
c.CRDN_YR,
c.CRDN_NO,
c.SGG_CD,
sgg.CD_NM AS SGG_CD_NM,
c.RGN_SE_CD,
rgn.CD_NM AS RGN_SE_CD_NM,
c.DSCL_MTHD_CD,
dscl.CD_NM AS DSCL_MTHD_CD_NM,
c.DSCL_YMD,
c.EXMNR,
c.RMRK,
c.DSPS_BFHD_BGNG_YMD,
c.DSPS_BFHD_END_YMD,
c.CRC_CMD_BGNG_YMD,
c.CRC_CMD_END_YMD,
c.CRC_URG_BGNG_YMD,
c.CRC_URG_END_YMD,
c.LEVY_PRVNTC_BGNG_YMD,
c.LEVY_PRVNTC_END_YMD,
c.LEVY_BGNG_YMD,
c.LEVY_END_YMD,
c.PAY_URG_BGNG_YMD,
c.PAY_URG_END_YMD,
c.FRST_CRDN_YR,
c.FRST_CRDN_NO,
c.RELEVY_YN,
c.AGRVTN_LEVY_TRGT_YN,
c.CRDN_PRCS_STTS_CD,
stts.CD_NM AS CRDN_PRCS_STTS_CD_NM,
c.CRDN_PRCS_YMD,
c.REG_DT,
c.RGTR,
u.USER_ACNT AS RGTR_ACNT,
u.USER_NM AS RGTR_NM,
p.STDG_EMD_CD,
emd.CD_NM AS STDG_EMD_CD_NM,
p.LDCG_CD,
ld.CD_NM AS LDCG_CD_NM,
p.PTOUT,
p.OALP,
p.LOTNO_WHOL_ADDR,
p.ROAD_NM_WHOL_ADDR,
p.ZIP,
p.UDGD_YN_CD,
p.MTN_YN_CD
FROM tb_crdn c
INNER JOIN tb_pstn_info p ON c.CRDN_YR = p.CRDN_YR AND c.CRDN_NO = p.CRDN_NO AND p.DEL_YN = 'N'
LEFT JOIN tb_cd_detail sgg ON sgg.CD_GROUP_ID = 'ORG_CD' AND sgg.CD_ID = c.SGG_CD
LEFT JOIN tb_cd_detail rgn ON rgn.CD_GROUP_ID = 'RGN_SE_CD' AND rgn.CD_ID = c.RGN_SE_CD
LEFT JOIN tb_cd_detail dscl ON dscl.CD_GROUP_ID = 'DSCL_MTHD_CD' AND dscl.CD_ID = c.DSCL_MTHD_CD
LEFT JOIN tb_cd_detail stts ON stts.CD_GROUP_ID = 'CRDN_PRCS_STTS_CD' AND stts.CD_ID = c.CRDN_PRCS_STTS_CD
LEFT JOIN tb_user u ON u.USER_ID = c.RGTR
LEFT JOIN tb_cd_detail emd ON emd.CD_GROUP_ID = 'STDG_EMD_CD' AND emd.CD_ID = p.STDG_EMD_CD
LEFT JOIN tb_cd_detail ld ON ld.CD_GROUP_ID = 'LDCG_CD' AND ld.CD_ID = p.LDCG_CD
WHERE c.DEL_YN = 'N'
AND c.CRDN_YR = #{crdnYr}
AND c.CRDN_NO = #{crdnNo}
</select>
<!-- 지정된 년도의 다음 단속 번호 조회 -->
<select id="selectNextCrdnNo" parameterType="string" resultType="string">
/* CrdnRelevyMapper.selectNextCrdnNo : 다음 단속번호 조회 */
SELECT LPAD(NEXTVAL(seq_crdn_no_${crdnYr}), 6, '0') AS NEXT_CRDN_NO
</select>
<!-- 기본 단속 정보 복사 -->
<insert id="insertCrdnBasicInfo" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.insertCrdnBasicInfo : 기본 단속 정보 복사 */
INSERT INTO tb_crdn (
CRDN_YR, /* 단속 연도 - 현재 년도 */
CRDN_NO, /* 단속 번호 - 신규 생성 */
SGG_CD, /* 시군구 코드 */
RGN_SE_CD, /* 지역 구분 코드 */
DSCL_MTHD_CD, /* 적발 방법 코드 */
DSCL_YMD, /* 적발 일자 */
EXMNR, /* 조사원 */
RMRK, /* 비고 - 재부과 사유 추가 */
FRST_CRDN_YR, /* 최초 단속 연도 - 원본 단속 연도 */
FRST_CRDN_NO, /* 최초 단속 번호 - 원본 단속 번호 */
RELEVY_YN, /* 재부과 여부 - 'Y' */
AGRVTN_LEVY_TRGT_YN, /* 가중 부과 대상 여부 */
CRDN_PRCS_STTS_CD, /* 단속 처리 상태 코드 - 30 (시정명령) */
CRDN_PRCS_YMD, /* 단속 처리 일자 - 현재 일자 */
REG_DT,
RGTR,
DEL_YN
)
SELECT
#{newCrdnYr}, /* 현재 년도 */
#{newCrdnNo}, /* 신규 단속 번호 */
SGG_CD,
RGN_SE_CD,
DSCL_MTHD_CD,
DSCL_YMD,
EXMNR,
#{relevyRsn},
#{srcCrdnYr}, /* 원본 단속 연도 */
#{srcCrdnNo}, /* 원본 단속 번호 */
'Y', /* 재부과 여부 */
AGRVTN_LEVY_TRGT_YN,
'30', /* 시정명령 상태 */
NOW(), /* 현재 일자 */
NOW(),
#{rgtr},
'N'
FROM tb_crdn
WHERE CRDN_YR = #{srcCrdnYr}
AND CRDN_NO = #{srcCrdnNo}
AND DEL_YN = 'N'
</insert>
<!-- 위치 정보 복사 -->
<insert id="insertCrdnPstnInfo" parameterType="CrdnRelevyVO">
/ CrdnRelevyMapper.insertCrdnPstnInfo : 위치 정보 복사 /
INSERT INTO tb_pstn_info (
PSTN_INFO_ID,
CRDN_YR,
CRDN_NO,
SGG_CD,
STDG_EMD_CD,
LDCG_CD,
PTOUT,
OALP,
LOTNO_WHOL_ADDR,
ROAD_NM_WHOL_ADDR,
ZIP,
LOTNO_ADDR,
ROAD_NM_ADDR,
DTL_ADDR,
REF_ADDR,
PBADMS_ZONE_CD,
ROAD_NM_CD,
LOTNO_MNO,
LOTNO_SNO,
BLDG_MNO,
BLDG_SNO,
UDGD_YN_CD,
MTN_YN_CD,
RMRK,
REG_DT,
RGTR,
DEL_YN
)
SELECT
LPAD(NEXTVAL(seq_pstn_info_id), 10, '0'),
#{newCrdnYr},
#{newCrdnNo},
SGG_CD,
STDG_EMD_CD,
LDCG_CD,
PTOUT,
OALP,
LOTNO_WHOL_ADDR,
ROAD_NM_WHOL_ADDR,
ZIP,
LOTNO_ADDR,
ROAD_NM_ADDR,
DTL_ADDR,
REF_ADDR,
PBADMS_ZONE_CD,
ROAD_NM_CD,
LOTNO_MNO,
LOTNO_SNO,
BLDG_MNO,
BLDG_SNO,
UDGD_YN_CD,
MTN_YN_CD,
RMRK,
NOW(),
#{rgtr},
'N'
FROM tb_pstn_info
WHERE CRDN_YR = #{srcCrdnYr}
AND CRDN_NO = #{srcCrdnNo}
AND DEL_YN = 'N'
</insert>
<!-- 소유자 정보 복사 -->
<insert id="insertCrdnOwnrInfo" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.insertCrdnOwnrInfo : 소유자 정보 복사 */
INSERT INTO tb_ownr_info (
OWNR_INFO_ID,
SGG_CD,
CRDN_YR,
CRDN_NO,
PSTN_INFO_ID,
OWNR_ID,
REG_DT,
RGTR,
DEL_YN
)
SELECT
LPAD(NEXTVAL(seq_ownr_info_id), 10, '0'),
#{newCrdnYr},
#{newCrdnNo},
SGG_CD,
PSTN_INFO_ID,
OWNR_ID,
NOW(),
#{rgtr},
'N'
FROM tb_ownr_info
WHERE CRDN_YR = #{srcCrdnYr}
AND CRDN_NO = #{srcCrdnNo}
AND DEL_YN = 'N'
</insert>
<!-- 불법행위 정보 복사 (조치완료 제외) -->
<insert id="insertCrdnActInfo" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.insertCrdnActInfo : 불법행위 정보 복사 (조치완료 제외) */
INSERT INTO tb_act_info (
ACT_INFO_ID,
SGG_CD,
CRDN_YR,
CRDN_NO,
PSTN_INFO_ID,
ACT_BGNG_YMD,
ACT_TYPE_CD,
ACT_NO,
VLTN_LWRG_CD_1,
VLTN_LWRG_CD_2,
STRCT_IDX_CD,
USG_IDX_CD,
HGT,
WDTH,
VRTC,
AREA,
RMRK,
ACTN_LAST_YMD,
ACTN_WHOL_AREA,
ACTN_PRCS_STTS_CD,
REG_DT,
RGTR,
DEL_YN
)
SELECT
LPAD(NEXTVAL(seq_act_info_id), 10, '0'),
SGG_CD,
#{newCrdnYr},
#{newCrdnNo},
PSTN_INFO_ID,
ACT_BGNG_YMD,
ACT_TYPE_CD,
ACT_NO,
VLTN_LWRG_CD_1,
VLTN_LWRG_CD_2,
STRCT_IDX_CD,
USG_IDX_CD,
HGT,
WDTH,
VRTC,
AREA,
RMRK,
ACTN_LAST_YMD,
ACTN_WHOL_AREA,
ACTN_PRCS_STTS_CD,
NOW(),
#{rgtr},
'N'
FROM tb_act_info
WHERE CRDN_YR = #{srcCrdnYr}
AND CRDN_NO = #{srcCrdnNo}
AND DEL_YN = 'N'
AND (ACTN_PRCS_STTS_CD != '3' OR ACTN_PRCS_STTS_CD IS NULL) /* 조치완료 제외 */
</insert>
<!-- 첨부파일 정보 복사 (조치완료 제외) -->
<insert id="insertCrdnPhotoInfo" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.insertCrdnPhotoInfo : 첨부파일 정보 복사 (조치완료 제외) */
INSERT INTO tb_crdn_photo (
ACT_INFO_ID,
CRDN_PHOTO_SN,
SGG_CD,
CRDN_YR,
CRDN_NO,
CRDN_PHOTO_PATH,
CRDN_PHOTO_NM,
CRDN_PHOTO_SE_CD,
ORGNL_PHOTO_NM,
ACTN_INFO_ID,
REG_DT,
RGTR,
DEL_YN
)
SELECT
a_new.ACT_INFO_ID, /* 새로 생성된 ACT_INFO_ID */
p.CRDN_PHOTO_SN,
p.SGG_CD,
#{newCrdnYr},
#{newCrdnNo},
p.CRDN_PHOTO_PATH,
p.CRDN_PHOTO_NM, /* 물리적 파일 복사 후 서비스에서 업데이트 */
p.CRDN_PHOTO_SE_CD,
p.ORGNL_PHOTO_NM,
p.ACTN_INFO_ID,
NOW(),
#{rgtr},
'N'
FROM tb_crdn_photo p
INNER JOIN tb_act_info a_src ON a_src.CRDN_YR = p.CRDN_YR
AND a_src.CRDN_NO = p.CRDN_NO
AND a_src.ACT_INFO_ID = p.ACT_INFO_ID
AND a_src.DEL_YN = 'N'
INNER JOIN tb_act_info a_new ON a_new.CRDN_YR = #{newCrdnYr}
AND a_new.CRDN_NO = #{newCrdnNo}
AND a_new.ACT_NO = a_src.ACT_NO
AND a_new.DEL_YN = 'N'
WHERE p.CRDN_YR = #{srcCrdnYr}
AND p.CRDN_NO = #{srcCrdnNo}
AND p.DEL_YN = 'N'
AND (a_src.ACTN_PRCS_STTS_CD != '3' OR a_src.ACTN_PRCS_STTS_CD IS NULL) /* 조치완료 제외 */
</insert>
<!-- 행위자 정보 복사 (조치완료 제외) -->
<insert id="insertCrdnActrInfo" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.insertCrdnActrInfo : 행위자 정보 복사 (조치완료 제외) */
INSERT INTO tb_actr_info (
ACTR_INFO_ID,
SGG_CD,
CRDN_YR,
CRDN_NO,
ACT_INFO_ID,
OWNR_ID,
REG_DT,
RGTR,
DEL_YN
)
SELECT
LPAD(NEXTVAL(seq_actr_info_id), 10, '0'),
a_new.SGG_CD,
a_new.CRDN_YR,
a_new.CRDN_NO,
a_new.ACT_INFO_ID, /* 새로 생성된 ACT_INFO_ID */
ai.OWNR_ID,
NOW(),
#{rgtr},
'N'
FROM tb_actr_info ai
INNER JOIN tb_act_info a_src ON a_src.ACT_INFO_ID = ai.ACT_INFO_ID
AND a_src.DEL_YN = 'N'
INNER JOIN tb_act_info a_new ON a_new.CRDN_YR = #{newCrdnYr}
AND a_new.CRDN_NO = #{newCrdnNo}
AND a_new.ACT_NO = a_src.ACT_NO
AND a_new.DEL_YN = 'N'
WHERE a_src.CRDN_YR = #{srcCrdnYr}
AND a_src.CRDN_NO = #{srcCrdnNo}
AND ai.DEL_YN = 'N'
AND (a_src.ACTN_PRCS_STTS_CD != '3' OR a_src.ACTN_PRCS_STTS_CD IS NULL) /* 조치완료 제외 */
</insert>
<!-- 단속 상태 업데이트 -->
<update id="updateCrdnStatus" parameterType="CrdnRelevyVO">
/* CrdnRelevyMapper.updateCrdnStatus : 단속 상태 업데이트 */
UPDATE tb_crdn
SET CRDN_PRCS_STTS_CD = '30', /* 시정명령 */
FRST_CRDN_YR = #{srcCrdnYr}, /* 최초 단속 연도 */
FRST_CRDN_NO = #{srcCrdnNo}, /* 최초 단속 번호 */
RELEVY_YN = 'Y' /* 재부과 여부 */
WHERE CRDN_YR = #{newCrdnYr}
AND CRDN_NO = #{newCrdnNo}
AND DEL_YN = 'N'
</update>
<!-- 원본 단속의 첨부파일 목록 조회 (조치완료 제외) -->
<select id="selectSrcPhotoList" parameterType="map" resultType="CrdnPhotoVO">
/* CrdnRelevyMapper.selectSrcPhotoList : 원본 첨부파일 목록 조회 */
SELECT
p.ACT_INFO_ID,
p.CRDN_PHOTO_SN,
p.SGG_CD,
p.CRDN_YR,
p.CRDN_NO,
p.CRDN_PHOTO_PATH,
p.CRDN_PHOTO_NM,
p.CRDN_PHOTO_SE_CD,
p.ORGNL_PHOTO_NM,
p.ACTN_INFO_ID
FROM tb_crdn_photo p
INNER JOIN tb_act_info a ON a.ACT_INFO_ID = p.ACT_INFO_ID
AND a.DEL_YN = 'N'
WHERE a.CRDN_YR = #{srcCrdnYr}
AND a.CRDN_NO = #{srcCrdnNo}
AND p.DEL_YN = 'N'
AND (a.ACTN_PRCS_STTS_CD != '3' OR a.ACTN_PRCS_STTS_CD IS NULL) /* 조치완료 제외 */
ORDER BY p.ACT_INFO_ID, p.CRDN_PHOTO_SN
</select>
<!-- 재부과 첨부파일 정보 업데이트 -->
<update id="updateRelevyPhotoInfo" parameterType="map">
/* CrdnRelevyMapper.updateRelevyPhotoInfo : 재부과 첨부파일 정보 업데이트 */
UPDATE tb_crdn_photo
SET CRDN_PHOTO_NM = #{crdnPhotoNm}
WHERE CRDN_YR = #{newCrdnYr}
AND CRDN_NO = #{newCrdnNo}
AND CRDN_PHOTO_SN = #{crdnPhotoSn}
AND DEL_YN = 'N'
</update>
</mapper>

@ -0,0 +1,226 @@
<%@ 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" %>
<div class="popup_wrap">
<div class="popup_inner">
<div class="popup_tit">
<h2 class="tit">재부과 등록</h2>
<a href="#" class="pop-x-btn modalclose"></a>
</div>
<div class="popup_con">
<div class="forms_table_non">
<form id="relevyForm" name="relevyForm">
<input type="hidden" id="mode" name="mode" value="${mode}" />
<input type="hidden" id="srcCrdnYr" name="srcCrdnYr" value="${srcCrdnYr}" />
<input type="hidden" id="srcCrdnNo" name="srcCrdnNo" value="${srcCrdnNo}" />
<table>
<colgroup>
<col style="width: 20%;" />
<col style="width: 30%;" />
<col style="width: 20%;" />
<col style="width: 30%;" />
</colgroup>
<tr>
<th class="th">단속 년도</th>
<td id="orgCrdnYr"></td>
<th class="th">단속 번호</th>
<td id="orgCrdnNo"></td>
</tr>
<tr>
<th class="th">지역구분</th>
<td id="orgRgnSeCdNm"></td>
<th class="th">법정동</th>
<td id="orgStdgEmdCdNm"></td>
</tr>
<tr>
<th class="th">적발방법</th>
<td id="orgDsclMthdCdNm"></td>
<th class="th">적발일자</th>
<td id="orgDsclYmd"></td>
</tr>
<tr>
<th class="th">조사원</th>
<td id="orgExmnr"></td>
<th class="th">현재 진행단계</th>
<td id="orgCrdnPrcsSttsCdNm"></td>
</tr>
<tr>
<th class="th">위치</th>
<td colspan="3" id="orgLotnoWholAddr"></td>
</tr>
<tr>
<th class="th"><span class="required">*</span> 재부과 사유</th>
<td colspan="3">
<textarea id="relevyRsn" name="relevyRsn" class="textarea" style="width: 100%; height: 80px;" rows="3" placeholder="재부과 사유를 입력해주세요." validation-check="required" maxlength="1000"></textarea>
</td>
</tr>
</table>
</form>
</div>
</div>
<!-- 팝업 버튼 -->
<div class="popup_foot">
<a href="#" id="btnSave" class="newbtns bg1">저장</a>
<a href="#" class="newbtns bg2 modalclose">닫기</a>
</div>
</div>
</div>
<script type="text/javascript">
(function(window, $) {
'use strict';
var RelevyPopup = {
/**
* 원본 단속 정보
*/
orgCrdnInfo: null,
/**
* 원본 단속 정보 조회
*/
loadOrgCrdnInfo: function() {
var self = this;
var srcCrdnYr = $("#srcCrdnYr").val();
var srcCrdnNo = $("#srcCrdnNo").val();
if (!srcCrdnYr || !srcCrdnNo) {
alert('원본 단속 정보가 없습니다.');
window.close();
return;
}
$.ajax({
url: '<c:url value="/crdn/crndRegistAndView/crdnRelevy/selectOrgCrdnInfo.ajax"/>',
type: 'GET',
data: {
crdnYr: srcCrdnYr,
crdnNo: srcCrdnNo
},
success: function(response) {
if (response && response.success && response.data) {
self.orgCrdnInfo = response.data;
self.displayOrgCrdnInfo();
} else {
alert('원본 단속 정보 조회에 실패했습니다.');
window.close();
}
},
error: function(xhr, status, error) {
console.error('원본 단속 정보 조회 실패:', error);
alert('원본 단속 정보 조회 중 오류가 발생했습니다.');
window.close();
}
});
},
/**
* 원본 단속 정보 화면 표시
*/
displayOrgCrdnInfo: function() {
var data = this.orgCrdnInfo;
$("#orgCrdnYr").text(data.crdnYr || '');
$("#orgCrdnNo").text(data.crdnNo || '');
$("#orgRgnSeCdNm").text(data.rgnSeCdNm || '');
$("#orgStdgEmdCdNm").text(data.stdgEmdCdNm || '');
$("#orgLotnoWholAddr").text( '('+data.zip+') ' + data.lotnoWholAddr);
$("#orgDsclMthdCdNm").text(data.dsclMthdCdNm || '');
$("#orgDsclYmd").text(data.dsclYmd ? moment(data.dsclYmd).format('YYYY-MM-DD') : '');
$("#orgExmnr").text(data.exmnr || '');
$("#orgCrdnPrcsSttsCdNm").text(data.crdnPrcsSttsCdNm || '');
},
/**
* 재부과 저장
*/
saveRelevy: function() {
var self = this;
if (!self.validate()) return;
if (!confirm('재부과를 등록하시겠습니까?\n\n원본 단속 정보를 복사하여 새로운 단속 건으로 등록됩니다.')) {
return;
}
var formData = $("#relevyForm").serialize();
$.ajax({
url: '<c:url value="/crdn/crndRegistAndView/crdnRelevy/saveRelevy.ajax"/>',
type: 'POST',
data: formData,
success: function(response) {
if (response && response.success) {
alert('재부과가 성공적으로 등록되었습니다.\n새로운 단속번호: ' + response.data.newCrdnNo);
// 부모 창의 목록 새로고침
if (window.opener && window.opener.refreshCrdnList) {
window.opener.refreshCrdnList();
}
// 팝업 닫기
window.close();
} else {
alert(response.message || '재부과 등록에 실패했습니다.');
}
},
error: function(xhr, status, error) {
console.error('재부과 등록 실패:', error);
alert('재부과 등록 중 오류가 발생했습니다.');
}
});
},
validate: function() {
var isValid = validateFormByAttributes('relevyForm');
if( isValid ){
// 재부과 사유 글자수 검증 (varchar(1000) 제한), validateFormByAttributes 처리되나, sample 로 남겨놓음
var relevyRsn = $.trim($('#relevyRsn').val());
if (relevyRsn && relevyRsn.length > 1000) {
var relevyRsnElement = document.getElementById('relevyRsn');
errorElementCreate(relevyRsnElement, '재부과 사유는 1000자 이내로 입력하세요. (현재: ' + relevyRsn.length + '자)', false);
$('#relevyRsn').focus();
return false;
}
}
return isValid;
},
/**
* 이벤트 바인딩
*/
bindEvents: function() {
var self = this;
// 저장 버튼 클릭
$("#btnSave").on('click', function(e) {
e.preventDefault();
self.saveRelevy();
});
// 팝업 닫기
$(".modalclose").on('click', function(e) {
e.preventDefault();
window.close();
});
},
/**
* 초기화
*/
init: function() {
this.bindEvents();
this.loadOrgCrdnInfo();
}
};
// DOM 준비 완료 시 초기화
$(document).ready(function() {
RelevyPopup.init();
});
})(window, jQuery);
</script>

@ -101,6 +101,7 @@
<button type="button" id="btnLevyPrvntc" class="newbtn bg2-1">부과예고</button>
<button type="button" id="btnLevy" class="newbtn bg2-1">부과</button>
<button type="button" id="btnPayUrg" class="newbtn bg2-1">납부촉구</button>
<button type="button" id="btnRelevy" class="newbtn bg3">재부과</button>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
<select id="perPageSelect" class="input" style="width: 112px; ">
@ -525,6 +526,19 @@
});
},
/**
* 재부과 팝업을 엽니다.
*
* @param crdnYr 단속 연도
* @param crdnNo 단속 번호
*/
openRelevyPopup: function(crdnYr, crdnNo) {
var url = '<c:url value="/crdn/crndRegistAndView/crdnRelevy/relevyPopup.do"/>?mode=C' +
'&srcCrdnYr=' + encodeURIComponent(crdnYr) +
'&srcCrdnNo=' + encodeURIComponent(crdnNo);
openPopup(url, 1200, 490, 'relevyPopup');
},
/**
* 목록 현재 페이징 새로고침
*/
@ -691,6 +705,18 @@
self.updateStatus('70', '납부촉구');
});
// 재부과 버튼 클릭 이벤트
$("#btnRelevy").on('click', function() {
// 선택된 행 확인
if (!self.selectedRow) {
alert('재부과를 진행할 단속 건을 선택해주세요.');
return;
}
// 재부과 팝업 열기
self.openRelevyPopup(self.selectedRow.crdnYr, self.selectedRow.crdnNo);
});
// 엔터키 검색
$(".gs_b_top input").on('keypress', function(e) {
if (e.which === 13) {

@ -88,8 +88,7 @@ function validateByAttribute(element) {
}
const validationType = element.getAttribute('validation-check');
if (!validationType
|| element.getAttribute('disabled')
if ( element.getAttribute('disabled')
|| element.getAttribute('disabled') === 'disabled'
|| element.getAttribute('disabled') === 'true'
) {
@ -208,7 +207,7 @@ function validateByAttribute(element) {
default:
// 지원하지 않는 validation 타입은 무시
console.warn(`지원하지 않는 validation 타입: ${type}`);
console.debug(`type이 없거나 지원하지 않는 validation 타입: ${type}`);
break;
/*

Loading…
Cancel
Save