재부과 신규로직 init

dev
박성영 3 months ago
parent a022fb939d
commit e5974bcca6

@ -1,36 +1,33 @@
create table tb_act_info
(
ACT_INFO_ID varchar(10) not null comment '행위 정보 ID'
ACT_INFO_ID varchar(10) not null comment '행위 정보 ID'
primary key,
SGG_CD varchar(5) not null comment '시군구 코드',
CRDN_YR char(4) null comment '단속 연도',
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 '행위 유형 코드',
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 '구조 지수 코드',
USG_IDX_CD varchar(5) null comment '용도 지수 코드',
HGT decimal(10, 2) null comment '높이',
WDTH decimal(10, 2) null comment '가로',
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 '수정 일시',
MDFR varchar(11) null comment '수정자',
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
SGG_CD varchar(5) not null comment '시군구 코드',
CRDN_YR char(4) null comment '단속 연도',
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(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 '구조 지수 코드',
USG_IDX_CD varchar(5) null comment '용도 지수 코드',
HGT decimal(10, 2) null comment '높이',
WDTH decimal(10, 2) null comment '가로',
VRTC decimal(10, 2) null comment '세로',
AREA decimal(10, 2) null comment '면적',
RMRK varchar(1000) 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 '삭제자',
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;

@ -1,19 +1,22 @@
create table tb_strct_idx
(
STRCT_IDX_CD varchar(3) not null comment '구조 지수 코드'
STRCT_IDX_CD varchar(3) not null comment '구조 지수 코드'
primary key,
STRCT_NM varchar(100) null comment '구조 명',
STRCT_IDX 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 '감가상각률'
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 '삭제자'
)
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