사전처분 초기버전 진행중...

dev
박성영 3 months ago
parent 8e76911588
commit ddf09b1f22

@ -0,0 +1,15 @@
-- 이행정보 ID 시퀀스
CREATE SEQUENCE seq_implt_info_id
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9999999999
CYCLE;
-- 이행 대상자 정보 ID 시퀀스
CREATE SEQUENCE seq_implt_trpr_info_id
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9999999999
CYCLE;

@ -0,0 +1,106 @@
package egovframework.constant;
/**
* packageName : egovframework.constant
* fileName : ImpltTaskSeConstants
* author :
* date : 2025-09-08
* description :
* : ,
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
public class ImpltTaskSeConstants {
/**
* - 1:
*
*/
public static final String IMPLT_TASK_SE_CD_1_DSPS_BFHD = "1";
/**
* - 2:
*
*/
public static final String IMPLT_TASK_SE_CD_2_CRC_CMD = "2";
/**
* - 3:
*
*/
public static final String IMPLT_TASK_SE_CD_3_CRC_URG = "3";
/**
* - 4:
*
*/
public static final String IMPLT_TASK_SE_CD_4_LEVY_PRVNTC = "4";
/**
* - 5:
*
*/
public static final String IMPLT_TASK_SE_CD_5_LEVY = "5";
/**
* - 6:
*
*/
public static final String IMPLT_TASK_SE_CD_6_PAY_URG = "6";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_DSPS_BFHD = "처분사전";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_CRC_CMD = "시정명령";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_CRC_URG = "시정촉구";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_LEVY_PRVNTC = "부과예고";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_LEVY = "부과";
/**
* -
*/
public static final String IMPLT_TASK_SE_CD_NM_PAY_URG = "납부촉구";
/**
*
*/
public static final String IMPLT_TASK_SE_CD_NM_UNKNOWN = "알수없음";
/**
* .
*
* @param impltTaskSeCd
* @return
*/
public static String getImpltTaskSeCdNm(String impltTaskSeCd) {
switch (impltTaskSeCd) {
case IMPLT_TASK_SE_CD_1_DSPS_BFHD: return IMPLT_TASK_SE_CD_NM_DSPS_BFHD;
case IMPLT_TASK_SE_CD_2_CRC_CMD: return IMPLT_TASK_SE_CD_NM_CRC_CMD;
case IMPLT_TASK_SE_CD_3_CRC_URG: return IMPLT_TASK_SE_CD_NM_CRC_URG;
case IMPLT_TASK_SE_CD_4_LEVY_PRVNTC: return IMPLT_TASK_SE_CD_NM_LEVY_PRVNTC;
case IMPLT_TASK_SE_CD_5_LEVY: return IMPLT_TASK_SE_CD_NM_LEVY;
case IMPLT_TASK_SE_CD_6_PAY_URG: return IMPLT_TASK_SE_CD_NM_PAY_URG;
default: return IMPLT_TASK_SE_CD_NM_UNKNOWN;
}
}
}

@ -30,4 +30,7 @@ public interface CrdnPstnInfoMapper {
/** 삭제 (논리삭제) */
int deletePstnInfo(@Param("pstnInfoId") String pstnInfoId, @Param("dltr") String dltr);
/** 다른로직 사용 용도, 사용중인 1건만 조회 */
CrdnPstnInfoVO selectPstnOne(CrdnPstnInfoVO vo);
}

@ -9,7 +9,9 @@ import java.util.List;
*/
public interface CrdnPstnInfoService {
List<CrdnPstnInfoVO> selectPstnInfoList(CrdnPstnInfoVO vo);
int selectPstnInfoListTotalCount(CrdnPstnInfoVO vo);
int insertPstnInfo(CrdnPstnInfoVO vo);
// 중요로직: 상세 조회 - 그리드 더블클릭 시 사용
@ -20,4 +22,7 @@ public interface CrdnPstnInfoService {
// 중요로직: 다중 삭제 - 체크박스로 선택된 위치정보들을 논리 삭제
int deletePstnInfos(List<String> pstnInfoIds, String dltr);
// 다른로직 사용 용도, 사용중인 1건만 조회
CrdnPstnInfoVO selectPstnOne(CrdnPstnInfoVO vo);
}

@ -104,4 +104,11 @@ public class CrdnPstnInfoServiceImpl extends EgovAbstractServiceImpl implements
return deletedCount;
}
// 다른로직 사용 용도, 사용중인 1건만 조회
@Override
public CrdnPstnInfoVO selectPstnOne(CrdnPstnInfoVO vo){
return mapper.selectPstnOne(vo);
}
}

@ -0,0 +1,258 @@
package go.kr.project.crdn.crndRegistAndView.main.controller;
import egovframework.constant.ImpltTaskSeConstants;
import egovframework.constant.TilesConstants;
import egovframework.exception.MessageException;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.crdn.crndRegistAndView.crdnPstnInfo.model.CrdnPstnInfoVO;
import go.kr.project.crdn.crndRegistAndView.crdnPstnInfo.service.CrdnPstnInfoService;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTaskVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTrprInfoVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRegistAndViewVO;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnImpltTaskService;
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.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.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.controller
* fileName : CrdnImpltTaskController
* author :
* date : 2025-08-25
* description : /
* : CRUD API
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-08-25
*/
@Controller
@RequestMapping("/crdn/crndRegistAndView/crdnImpltTask")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "이행정보 등록/조회", description = "이행정보 등록/조회 관련 API")
public class CrdnImpltTaskController {
/* 단속 서비스 */
private final CrdnRegistAndViewService crdnRegistAndViewService;
/* 위치정보 서비스 */
private final CrdnPstnInfoService crdnPstnInfoService;
/* 이행정보 서비스 */
private final CrdnImpltTaskService crdnImpltTaskService;
/**
* .
*
* @param crdnYr
* @param crdnNo
* @param impltTaskSeCd (1:, 2:, 3:, 4:, 5:, 6:)
* @param mode (C:, U:, V:)
* @param model
* @return
*/
@GetMapping("/impltTaskPopup.do")
@Operation(summary = "이행정보 관리 팝업", description = "이행정보 관리 팝업 화면을 제공합니다.")
public ModelAndView impltTaskPopup(
@Parameter(description = "단속 연도") @RequestParam String crdnYr,
@Parameter(description = "단속 번호") @RequestParam String crdnNo,
@Parameter(description = "이행업무구분코드") @RequestParam String impltTaskSeCd,
@Parameter(description = "모드") @RequestParam(defaultValue = "C") String mode,
Model model) {
log.debug("이행정보 관리 팝업 요청 - 단속연도: {}, 단속번호: {}, 이행업무구분코드: {}, 모드: {}",
crdnYr, crdnNo, impltTaskSeCd, mode);
ModelAndView mav = new ModelAndView("crdn/crndRegistAndView/main/crdnImpltTask/impltTaskPopup" + TilesConstants.POPUP);
// 기본 파라미터 설정
mav.addObject("crdnYr", crdnYr);
mav.addObject("crdnNo", crdnNo);
mav.addObject("impltTaskSeCd", impltTaskSeCd);
mav.addObject("mode", mode);
// 이행업무구분코드명 설정
String impltTaskSeCdNm = ImpltTaskSeConstants.getImpltTaskSeCdNm(impltTaskSeCd);
mav.addObject("impltTaskSeCdNm", impltTaskSeCdNm);
// 단속정보
CrdnRegistAndViewVO crdnParamVO = new CrdnRegistAndViewVO();
crdnParamVO.setCrdnYr(crdnYr);
crdnParamVO.setCrdnNo(crdnNo);
CrdnRegistAndViewVO crdnData = crdnRegistAndViewService.selectOne(crdnParamVO);
mav.addObject("crdnData", crdnData);
// 위치정보
CrdnPstnInfoVO pstnParamVO = new CrdnPstnInfoVO();
pstnParamVO.setCrdnYr(crdnYr);
pstnParamVO.setCrdnNo(crdnNo);
CrdnPstnInfoVO pstnData = crdnPstnInfoService.selectPstnOne(pstnParamVO);
if( pstnData == null ){
throw new MessageException("위치정보를 찾을 수 없습니다.");
}
mav.addObject("pstnData", pstnData);
// 수정/보기 모드인 경우 기존 이행정보 조회
if ("U".equals(mode) || "V".equals(mode)) {
CrdnImpltTaskVO searchVO = new CrdnImpltTaskVO();
searchVO.setCrdnYr(crdnYr);
searchVO.setCrdnNo(crdnNo);
searchVO.setImpltTaskSeCd(impltTaskSeCd);
CrdnImpltTaskVO impltTaskInfo = crdnImpltTaskService.selectImpltInfo(searchVO);
if (impltTaskInfo != null) {
mav.addObject("impltTaskInfo", impltTaskInfo);
}
}
return mav;
}
/**
* .
*
* @param crdnYr
* @param crdnNo
* @param impltTaskSeCd
* @return
*/
@GetMapping("/selectImpltInfo.ajax")
@Operation(summary = "이행정보 조회", description = "이행정보를 조회합니다.")
public ResponseEntity<?> selectImpltInfo(
@Parameter(description = "단속 연도") @RequestParam String crdnYr,
@Parameter(description = "단속 번호") @RequestParam String crdnNo,
@Parameter(description = "이행업무구분코드") @RequestParam String impltTaskSeCd) {
log.debug("이행정보 조회 요청 - 단속연도: {}, 단속번호: {}, 이행업무구분코드: {}", crdnYr, crdnNo, impltTaskSeCd);
// 단속정보
CrdnRegistAndViewVO crdnParamVO = new CrdnRegistAndViewVO();
crdnParamVO.setCrdnYr(crdnYr);
crdnParamVO.setCrdnNo(crdnNo);
CrdnRegistAndViewVO crdnData = crdnRegistAndViewService.selectOne(crdnParamVO);
if( crdnData == null ){
throw new MessageException("단속정보를 찾을 수 없습니다.");
}
// 위치정보
CrdnPstnInfoVO pstnParamVO = new CrdnPstnInfoVO();
pstnParamVO.setCrdnYr(crdnYr);
pstnParamVO.setCrdnNo(crdnNo);
CrdnPstnInfoVO pstnData = crdnPstnInfoService.selectPstnOne(pstnParamVO);
if( pstnData == null ){
throw new MessageException("위치정보를 찾을 수 없습니다. 위치정보 등록 후 "+ImpltTaskSeConstants.getImpltTaskSeCdNm(impltTaskSeCd)+" 등록이 가능합니다.");
}
CrdnImpltTaskVO paramVO = new CrdnImpltTaskVO();
paramVO.setCrdnYr(crdnYr);
paramVO.setCrdnNo(crdnNo);
paramVO.setImpltTaskSeCd(impltTaskSeCd);
CrdnImpltTaskVO result = crdnImpltTaskService.selectImpltInfo(paramVO);
if (result != null) {
return ApiResponseUtil.success(result, "이행정보 조회가 완료되었습니다.");
} else {
return ApiResponseUtil.success(null, "등록된 이행정보가 없습니다.");
}
}
/**
* .
*
* @param paramVO VO
* @return
*/
@PostMapping("/insertImpltInfo.ajax")
@Operation(summary = "이행정보 등록", description = "이행정보를 등록합니다.")
public ResponseEntity<?> insertImpltInfo(@ModelAttribute CrdnImpltTaskVO paramVO) {
log.debug("이행정보 등록 요청 - 단속연도: {}, 단속번호: {}, 이행업무구분코드: {}",
paramVO.getCrdnYr(), paramVO.getCrdnNo(), paramVO.getImpltTaskSeCd());
paramVO.setRgtr(SessionUtil.getUserId());
int result = crdnImpltTaskService.insertImpltInfo(paramVO);
if (result > 0) {
return ApiResponseUtil.success("이행정보가 성공적으로 등록되었습니다.");
} else {
return ApiResponseUtil.error("이행정보 등록에 실패했습니다.");
}
}
/**
* .
*
* @param paramVO VO
* @return
*/
@PostMapping("/updateImpltInfo.ajax")
@Operation(summary = "이행정보 수정", description = "이행정보를 수정합니다.")
public ResponseEntity<?> updateImpltInfo(@ModelAttribute CrdnImpltTaskVO paramVO) {
log.debug("이행정보 수정 요청 - ID: {}", paramVO.getImpltInfoId());
// 세션 정보 설정
paramVO.setMdfr(SessionUtil.getUserId());
int result = crdnImpltTaskService.updateImpltInfo(paramVO);
if (result > 0) {
return ApiResponseUtil.success("이행정보가 성공적으로 수정되었습니다.");
} else {
return ApiResponseUtil.error("이행정보 수정에 실패했습니다.");
}
}
/**
* .
*
* @param crdnYr
* @param crdnNo
* @return
*/
@GetMapping("/selectOwnrActrInfoList.ajax")
@Operation(summary = "소유자/행위자 정보 조회", description = "단속 정보에 해당하는 소유자/행위자 정보 목록을 조회합니다.")
public ResponseEntity<?> selectOwnrInfoList(
@Parameter(description = "단속 연도") @RequestParam String crdnYr,
@Parameter(description = "단속 번호") @RequestParam String crdnNo) {
log.debug("소유자 정보 조회 요청 - 단속연도: {}, 단속번호: {}", crdnYr, crdnNo);
CrdnImpltTaskVO paramVO = new CrdnImpltTaskVO();
paramVO.setCrdnYr(crdnYr);
paramVO.setCrdnNo(crdnNo);
List<CrdnImpltTrprInfoVO> ownrList = crdnImpltTaskService.selectOwnrInfoList(paramVO);
List<CrdnImpltTrprInfoVO> actrList = crdnImpltTaskService.selectActrInfoList(paramVO);
paramVO.setTotalCount(ownrList.size() + actrList.size());
List<CrdnImpltTrprInfoVO> list = new ArrayList<>();
list.addAll(ownrList);
list.addAll(actrList);
list.sort(Comparator.comparing(CrdnImpltTrprInfoVO::getActNo)
.thenComparing(CrdnImpltTrprInfoVO::getImpltTrprFlnm));
return ApiResponseUtil.successWithGrid(list, paramVO);
}
}

@ -1,6 +1,5 @@
package go.kr.project.crdn.crndRegistAndView.main.controller;
import egovframework.constant.CrdnPrcsSttsConstants;
import egovframework.constant.MessageConstants;
import egovframework.constant.TilesConstants;
import egovframework.exception.MessageException;
@ -10,6 +9,7 @@ import go.kr.project.common.model.CmmnCodeSearchVO;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnRegistAndViewVO;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnRegistAndViewService;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnImpltTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -47,6 +47,9 @@ public class CrdnRegistAndViewController {
/** 단속 서비스 */
private final CrdnRegistAndViewService service;
/** 이행정보 서비스 */
private final CrdnImpltTaskService crdnImpltTaskService;
private final CommonCodeService commonCodeService;
/**

@ -0,0 +1,114 @@
package go.kr.project.crdn.crndRegistAndView.main.mapper;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTaskVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTrprInfoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.impltTask.mapper
* fileName : ImpltTaskMapper
* author :
* date : 2025-09-08
* description : MyBatis
* : (tb_implt_info) (tb_implt_trpr_info) CRUD .
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
@Mapper
public interface CrdnImpltTaskMapper {
// ==================== 이행정보(TB_IMPLT_INFO) 관련 메서드 ====================
/**
* .
* @param vo (crdnYr, crdnNo) VO
* @return
*/
CrdnImpltTaskVO selectImpltInfo(CrdnImpltTaskVO vo);
/**
* .
* @param vo VO
* @return
*/
int insertImpltInfo(CrdnImpltTaskVO vo);
/**
* .
* @param vo VO
* @return
*/
int updateImpltInfo(CrdnImpltTaskVO vo);
/**
* . ( )
* @param vo ID VO
* @return
*/
int deleteImpltInfo(CrdnImpltTaskVO vo);
/**
* .
* @param vo (crdnYr, crdnNo) VO
* @return
*/
int selectImpltInfoExistsCount(CrdnImpltTaskVO vo);
// ==================== 이행 대상자 정보(TB_IMPLT_TRPR_INFO) 관련 메서드 ====================
/**
* .
* @param vo ID VO
* @return
*/
List<CrdnImpltTrprInfoVO> selectImpltTrprInfoList(CrdnImpltTaskVO vo);
/**
* .
* @param vo VO
* @return
*/
int insertImpltTrprInfo(CrdnImpltTrprInfoVO vo);
/**
* . ( )
* @param impltInfoId ID
* @return
*/
int deleteImpltTrprInfoByImpltInfoId(@Param("impltInfoId") String impltInfoId);
// ==================== 소유자/행위자 정보 조회 메서드 ====================
/**
* .
* @param vo (crdnYr, crdnNo) VO
* @return ( )
*/
List<CrdnImpltTrprInfoVO> selectOwnrInfoList(CrdnImpltTaskVO vo);
/**
* .
* @param vo (crdnYr, crdnNo) VO
* @return ( )
*/
List<CrdnImpltTrprInfoVO> selectActrInfoList(CrdnImpltTaskVO vo);
// ==================== 시퀀스 관련 메서드 ====================
/**
* ID 퀀 .
* @return 퀀 (10 )
*/
String selectNextImpltInfoId();
/**
* ID 퀀 .
* @return 퀀 (10 )
*/
String selectNextImpltTrprInfoId();
}

@ -0,0 +1,144 @@
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;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.impltTask.model
* fileName : ImpltTaskVO
* author :
* date : 2025-09-08
* description : Value Object
* : (tb_implt_info) VO .
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CrdnImpltTaskVO extends PagingVO {
// ==================== 기본 테이블 컬럼 (TB_IMPLT_INFO) ====================
/** 이행정보 ID */
private String impltInfoId;
/** 시군구 코드 */
private String sggCd;
/** 단속 연도 */
private String crdnYr;
/** 단속 번호 */
private String crdnNo;
/** 이행업무구분코드 (1:처분사전, 2:시정명령, 3:시정촉구, 4:부과예고, 5:부과, 6:납부촉구) */
private String impltTaskSeCd;
/** 이행 시작 일자 (행정처분 시작일) */
private String impltBgngYmd;
/** 이행 종료 일자 (행정처분 종료일) */
private String impltEndYmd;
/** 행정처분 간격일 (자동계산) */
private Integer impltDaysCnt;
/** 등록 일시 */
@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;
/** 수정 일시 */
@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 mdfcnDt;
/** 수정자 */
private String mdfr;
/** 삭제 여부 */
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 impltTaskSeCdNm;
/** 시군구 코드명 */
private String sggCdNm;
/** 지역구분코드 */
private String rgnSeCd;
/** 지역구분코드명 */
private String rgnSeCdNm;
/** 표준읍면동코드 */
private String stdgEmdCd;
/** 표준읍면동코드명 */
private String stdgEmdCdNm;
// ==================== 이행 대상자 정보 ====================
/** 이행 대상자 정보 리스트 */
private List<CrdnImpltTrprInfoVO> impltTrprInfoList;
/** 선택된 소유자/행위자 ID 배열 */
private String[] selectedOwnrActrIds;
/** 선택된 이행대상자구분코드 배열 (1:소유자, 2:행위자) */
private String[] selectedTrprSeCds;
// ==================== 검색 조건 ====================
/** 검색 조건 - 단속 연도 */
private String schCrdnYr;
/** 검색 조건 - 단속 번호 */
private String schCrdnNo;
/** 검색 조건 - 이행업무구분코드 */
private String schImpltTaskSeCd;
/** 검색 조건 - 이행 시작 일자 (시작) */
private String schImpltBgngYmdFrom;
/** 검색 조건 - 이행 시작 일자 (종료) */
private String schImpltBgngYmdTo;
// ==================== 추가 필드 ====================
/** 모드 (C:등록, U:수정, V:보기, D:삭제) */
private String mode;
/** 단속 정보 (조인용) */
private String dsclYmd; // 적발일자
private String exmnr; // 조사원
private String lotnoWholAddr; // 지번전체주소
private String lotnoAddr; // 지번주소
private String lotnoMno; // 지번본번
private String lotnoSno; // 지번부번
}

@ -0,0 +1,105 @@
package go.kr.project.crdn.crndRegistAndView.main.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.impltTask.model
* fileName : ImpltTrprInfoVO
* author :
* date : 2025-09-08
* description : Value Object
* : (tb_implt_trpr_info) VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CrdnImpltTrprInfoVO {
// ==================== 기본 테이블 컬럼 (TB_IMPLT_TRPR_INFO) ====================
/** 이행 대상자 정보 ID */
private String impltTrprInfoId;
/** 시군구 코드 */
private String sggCd;
/** 이행 정보 ID */
private String impltInfoId;
/** 이행 대상자 성명 */
private String impltTrprFlnm;
/** 이행 대상자 주소 */
private String impltTrprAddr;
/** 이행 대상자 상세주소 */
private String impltTrprDaddr;
/** 이행 대상자 우편번호 */
private String impltTrprZip;
/** 이행 대상자 구분 코드 (1:소유자, 2:행위자) */
private String impltTrprSeCd;
/** 소유자 행위자 정보 ID */
private String ownrActrInfoId;
/** 등록 일시 */
@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;
/** 수정 일시 */
@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 mdfcnDt;
/** 수정자 */
private String mdfr;
/** 삭제 여부 */
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 impltTrprSeCdNm;
/** 시군구 코드명 */
private String sggCdNm;
/** 행위 번호 */
private String actNo;
private String actTypeCd;
private String actTypeCdNm;
// ==================== 추가 필드 (체크박스 선택용) ====================
/** 체크박스 선택 여부 */
private boolean selected;
private String lotnoMno;
private String lotnoSno;
}

@ -0,0 +1,118 @@
package go.kr.project.crdn.crndRegistAndView.main.service;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTaskVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTrprInfoVO;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.impltTask.service
* fileName : ImpltTaskService
* author :
* date : 2025-09-08
* description :
* : CRUD .
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
public interface CrdnImpltTaskService {
// ==================== 이행정보 관련 메서드 ====================
/**
* .
*
* @param vo (crdnYr, crdnNo) VO
* @return ( )
*/
CrdnImpltTaskVO selectImpltInfo(CrdnImpltTaskVO vo);
/**
* .
*
* @param vo VO ( )
* @return
*/
int insertImpltInfo(CrdnImpltTaskVO vo);
/**
* .
*
* @param vo VO ( )
* @return
*/
int updateImpltInfo(CrdnImpltTaskVO vo);
/**
* . ( )
*
* @param vo ID VO
* @return
*/
int deleteImpltInfo(CrdnImpltTaskVO vo);
/**
* .
*
* @param vo (crdnYr, crdnNo) VO
* @return
*/
int selectImpltInfoExistsCount(CrdnImpltTaskVO vo);
// ==================== 이행 대상자 정보 관련 메서드 ====================
/**
* .
*
* @param vo ID VO
* @return
*/
List<CrdnImpltTrprInfoVO> selectImpltTrprInfoList(CrdnImpltTaskVO vo);
/**
* .
*
* @param vo VO
* @return
*/
int insertImpltTrprInfo(CrdnImpltTrprInfoVO vo);
/**
* . ( )
*
* @param impltInfoId ID
* @return
*/
int deleteImpltTrprInfoByImpltInfoId(String impltInfoId);
// ==================== 소유자/행위자 정보 조회 메서드 ====================
/**
* .
*
* @param vo (crdnYr, crdnNo) VO
* @return ( )
*/
List<CrdnImpltTrprInfoVO> selectOwnrInfoList(CrdnImpltTaskVO vo);
/**
* .
*
* @param vo (crdnYr, crdnNo) VO
* @return ( )
*/
List<CrdnImpltTrprInfoVO> selectActrInfoList(CrdnImpltTaskVO vo);
// ==================== 유틸리티 메서드 ====================
/**
* .
*
* @param startDate (YYYYMMDD)
* @param endDate (YYYYMMDD)
* @return
*/
int calculateDaysBetween(String startDate, String endDate);
}

@ -0,0 +1,262 @@
package go.kr.project.crdn.crndRegistAndView.main.service.impl;
import egovframework.exception.MessageException;
import egovframework.util.SessionUtil;
import egovframework.util.StringUtil;
import go.kr.project.crdn.crndRegistAndView.main.mapper.CrdnImpltTaskMapper;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTaskVO;
import go.kr.project.crdn.crndRegistAndView.main.model.CrdnImpltTrprInfoVO;
import go.kr.project.crdn.crndRegistAndView.main.service.CrdnImpltTaskService;
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 java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
/**
* packageName : go.kr.project.crdn.crndRegistAndView.main.impltTask.service.impl
* fileName : ImpltTaskServiceImpl
* author :
* date : 2025-09-08
* description :
* : CRUD , .
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-09-08
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CrdnCrdnImpltTaskServiceImpl extends EgovAbstractServiceImpl implements CrdnImpltTaskService {
private final CrdnImpltTaskMapper crdnImpltTaskMapper;
// ==================== 이행정보 관련 메서드 ====================
@Override
public CrdnImpltTaskVO selectImpltInfo(CrdnImpltTaskVO vo) {
log.debug("이행정보 조회 시작 - 단속연도: {}, 단속번호: {}, 이행업무구분코드: {}",
vo.getCrdnYr(), vo.getCrdnNo(), vo.getImpltTaskSeCd());
try {
CrdnImpltTaskVO result = crdnImpltTaskMapper.selectImpltInfo(vo);
if (result != null) {
// 이행 대상자 정보 조회
List<CrdnImpltTrprInfoVO> trprInfoList = crdnImpltTaskMapper.selectImpltTrprInfoList(result);
result.setImpltTrprInfoList(trprInfoList);
// 행정처분 간격일 계산
if (StringUtil.isNotEmpty(result.getImpltBgngYmd()) && StringUtil.isNotEmpty(result.getImpltEndYmd())) {
int daysCnt = calculateDaysBetween(result.getImpltBgngYmd(), result.getImpltEndYmd());
result.setImpltDaysCnt(daysCnt);
}
}
return result;
} catch (Exception e) {
log.error("이행정보 조회 중 오류 발생", e);
throw new MessageException("이행정보 조회 중 오류가 발생했습니다.");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertImpltInfo(CrdnImpltTaskVO vo) {
log.debug("이행정보 등록 시작 - 단속연도: {}, 단속번호: {}", vo.getCrdnYr(), vo.getCrdnNo());
// 유효성 검증
validateRequiredFields(vo);
// 중복 체크
int existsCount = crdnImpltTaskMapper.selectImpltInfoExistsCount(vo);
if (existsCount > 0) {
throw new MessageException("이미 등록된 이행정보가 존재합니다.");
}
// 이행정보 ID 생성
String impltInfoId = crdnImpltTaskMapper.selectNextImpltInfoId();
vo.setImpltInfoId(impltInfoId);
// 세션 정보 설정
vo.setRgtr(SessionUtil.getUserId());
vo.setDelYn("N");
// 이행정보 등록
int result = crdnImpltTaskMapper.insertImpltInfo(vo);
// 이행 대상자 정보 등록
if (vo.getImpltTrprInfoList() != null && !vo.getImpltTrprInfoList().isEmpty()) {
for (CrdnImpltTrprInfoVO trprInfo : vo.getImpltTrprInfoList()) {
String trprInfoId = crdnImpltTaskMapper.selectNextImpltTrprInfoId();
trprInfo.setImpltTrprInfoId(trprInfoId);
trprInfo.setImpltInfoId(impltInfoId);
trprInfo.setRgtr(SessionUtil.getUserId());
trprInfo.setDelYn("N");
crdnImpltTaskMapper.insertImpltTrprInfo(trprInfo);
}
}
log.debug("이행정보 등록 완료 - ID: {}", impltInfoId);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateImpltInfo(CrdnImpltTaskVO vo) {
log.debug("이행정보 수정 시작 - ID: {}", vo.getImpltInfoId());
// 유효성 검증
validateRequiredFields(vo);
// 세션 정보 설정
vo.setMdfr(SessionUtil.getUserId());
// 행정처분 간격일 계산
if (StringUtil.isNotEmpty(vo.getImpltBgngYmd()) && StringUtil.isNotEmpty(vo.getImpltEndYmd())) {
int daysCnt = calculateDaysBetween(vo.getImpltBgngYmd(), vo.getImpltEndYmd());
vo.setImpltDaysCnt(daysCnt);
}
// 이행정보 수정
int result = crdnImpltTaskMapper.updateImpltInfo(vo);
// 기존 이행 대상자 정보 삭제
crdnImpltTaskMapper.deleteImpltTrprInfoByImpltInfoId(vo.getImpltInfoId());
// 새로운 이행 대상자 정보 등록
if (vo.getImpltTrprInfoList() != null && !vo.getImpltTrprInfoList().isEmpty()) {
for (CrdnImpltTrprInfoVO trprInfo : vo.getImpltTrprInfoList()) {
String trprInfoId = crdnImpltTaskMapper.selectNextImpltTrprInfoId();
trprInfo.setImpltTrprInfoId(trprInfoId);
trprInfo.setImpltInfoId(vo.getImpltInfoId());
trprInfo.setRgtr(SessionUtil.getUserId());
trprInfo.setDelYn("N");
crdnImpltTaskMapper.insertImpltTrprInfo(trprInfo);
}
}
log.debug("이행정보 수정 완료 - ID: {}", vo.getImpltInfoId());
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteImpltInfo(CrdnImpltTaskVO vo) {
log.debug("이행정보 삭제 시작 - ID: {}", vo.getImpltInfoId());
// 세션 정보 설정
vo.setDltr(SessionUtil.getUserId());
// 이행정보 논리 삭제
int result = crdnImpltTaskMapper.deleteImpltInfo(vo);
// 이행 대상자 정보 물리 삭제
crdnImpltTaskMapper.deleteImpltTrprInfoByImpltInfoId(vo.getImpltInfoId());
log.debug("이행정보 삭제 완료 - ID: {}", vo.getImpltInfoId());
return result;
}
@Override
public int selectImpltInfoExistsCount(CrdnImpltTaskVO vo) {
return crdnImpltTaskMapper.selectImpltInfoExistsCount(vo);
}
// ==================== 이행 대상자 정보 관련 메서드 ====================
@Override
public List<CrdnImpltTrprInfoVO> selectImpltTrprInfoList(CrdnImpltTaskVO vo) {
return crdnImpltTaskMapper.selectImpltTrprInfoList(vo);
}
@Override
public int insertImpltTrprInfo(CrdnImpltTrprInfoVO vo) {
// 시퀀스 생성 및 세션 정보 설정
String trprInfoId = crdnImpltTaskMapper.selectNextImpltTrprInfoId();
vo.setImpltTrprInfoId(trprInfoId);
vo.setRgtr(SessionUtil.getUserId());
vo.setDelYn("N");
return crdnImpltTaskMapper.insertImpltTrprInfo(vo);
}
@Override
public int deleteImpltTrprInfoByImpltInfoId(String impltInfoId) {
return crdnImpltTaskMapper.deleteImpltTrprInfoByImpltInfoId(impltInfoId);
}
// ==================== 소유자/행위자 정보 조회 메서드 ====================
@Override
public List<CrdnImpltTrprInfoVO> selectOwnrInfoList(CrdnImpltTaskVO vo) {
return crdnImpltTaskMapper.selectOwnrInfoList(vo);
}
@Override
public List<CrdnImpltTrprInfoVO> selectActrInfoList(CrdnImpltTaskVO vo) {
return crdnImpltTaskMapper.selectActrInfoList(vo);
}
// ==================== 유틸리티 메서드 ====================
@Override
public int calculateDaysBetween(String startDate, String endDate) {
try {
if (StringUtil.isEmpty(startDate) || StringUtil.isEmpty(endDate)) {
return 0;
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate start = LocalDate.parse(startDate, formatter);
LocalDate end = LocalDate.parse(endDate, formatter);
return (int) ChronoUnit.DAYS.between(start, end);
} catch (Exception e) {
log.warn("날짜 계산 중 오류 발생 - 시작일: {}, 종료일: {}", startDate, endDate, e);
return 0;
}
}
// ==================== 유효성 검증 메서드 ====================
/**
* .
*/
private void validateRequiredFields(CrdnImpltTaskVO vo) {
if (StringUtil.isEmpty(vo.getCrdnYr())) {
throw new MessageException("단속 연도는 필수 입력 항목입니다.");
}
if (StringUtil.isEmpty(vo.getCrdnNo())) {
throw new MessageException("단속 번호는 필수 입력 항목입니다.");
}
if (StringUtil.isEmpty(vo.getImpltTaskSeCd())) {
throw new MessageException("이행업무구분코드는 필수 입력 항목입니다.");
}
if (StringUtil.isEmpty(vo.getImpltBgngYmd())) {
throw new MessageException("이행 시작일자는 필수 입력 항목입니다.");
}
if (StringUtil.isEmpty(vo.getImpltEndYmd())) {
throw new MessageException("이행 종료일자는 필수 입력 항목입니다.");
}
// 날짜 유효성 검증
if (vo.getImpltBgngYmd().compareTo(vo.getImpltEndYmd()) > 0) {
throw new MessageException("이행 시작일자는 종료일자보다 이전이어야 합니다.");
}
}
}

@ -232,4 +232,57 @@
AND DEL_YN = 'N'
</update>
<!-- 다른로직 사용 용도, 사용중인 1건만 조회 -->
<select id="selectPstnOne" parameterType="CrdnPstnInfoVO" resultType="CrdnPstnInfoVO">
/* PstnInfoMapper.selectPstnOne : 위치정보 다른로직 사용 용도, 사용중인 1건만 조회 */
SELECT
p.PSTN_INFO_ID,
p.SGG_CD,
sgg.CD_NM AS SGG_CD_NM,
p.CRDN_YR,
p.CRDN_NO,
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.LOTNO_ADDR,
p.ROAD_NM_ADDR,
p.DTL_ADDR,
p.REF_ADDR,
p.PBADMS_ZONE_CD,
p.ROAD_NM_CD,
p.LOTNO_MNO,
p.LOTNO_SNO,
p.BLDG_MNO,
p.BLDG_SNO,
p.UDGD_YN_CD,
p.MTN_YN_CD,
p.RMRK,
p.REG_DT,
p.RGTR,
regUser.USER_ACNT AS RGTR_ACNT,
regUser.USER_NM AS RGTR_NM,
p.MDFCN_DT,
p.MDFR,
modUser.USER_ACNT AS MDFR_ACNT,
modUser.USER_NM AS MDFR_NM,
p.DEL_YN,
p.DEL_DT,
p.DLTR
FROM tb_pstn_info p
LEFT JOIN tb_cd_detail sgg ON sgg.CD_GROUP_ID = 'ORG_CD' AND sgg.CD_ID = p.SGG_CD
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
LEFT JOIN tb_user regUser ON regUser.USER_ID = p.RGTR
LEFT JOIN tb_user modUser ON modUser.USER_ID = p.MDFR
WHERE p.CRDN_YR = #{crdnYr}
AND p.CRDN_NO = #{crdnNo}
AND p.DEL_YN = 'N'
</select>
</mapper>

@ -0,0 +1,259 @@
<?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.CrdnImpltTaskMapper">
<!-- ==================== 이행정보(TB_IMPLT_INFO) 관련 쿼리 ==================== -->
<!-- 이행정보 조회 -->
<select id="selectImpltInfo" parameterType="CrdnImpltTaskVO" resultType="CrdnImpltTaskVO">
SELECT
ii.IMPLT_INFO_ID,
ii.SGG_CD,
ii.CRDN_YR,
ii.CRDN_NO,
ii.IMPLT_TASK_SE_CD,
ii.IMPLT_BGNG_YMD,
ii.IMPLT_END_YMD,
ii.REG_DT,
ii.RGTR,
ii.MDFCN_DT,
ii.MDFR,
ii.DEL_YN,
ii.DEL_DT,
ii.DLTR,
-- 간격일 계산 (MariaDB DATEDIFF 함수 사용)
CASE
WHEN ii.IMPLT_BGNG_YMD IS NOT NULL AND ii.IMPLT_END_YMD IS NOT NULL
THEN DATEDIFF(STR_TO_DATE(ii.IMPLT_END_YMD, '%Y%m%d'), STR_TO_DATE(ii.IMPLT_BGNG_YMD, '%Y%m%d'))
ELSE 0
END as impltDaysCnt,
-- 단속 정보 (조인)
c.DSCL_YMD,
c.EXMNR,
c.RGN_SE_CD,
rgnSeCd.CD_NM as RGN_SE_CD_NM,
p.STDG_EMD_CD,
stdgEmdCd.CD_NM as STDG_EMD_CD_NM,
p.LOTNO_ADDR,
p.LOTNO_MNO,
p.LOTNO_SNO,
impltTaskSe.CD_NM as IMPLT_TASK_SE_CD_NM,
sggCd.CD_NM as sggCdNm
FROM TB_IMPLT_INFO ii
INNER JOIN tb_crdn c ON ii.CRDN_YR = c.CRDN_YR AND ii.CRDN_NO = c.CRDN_NO AND c.DEL_YN = 'N'
LEFT JOIN tb_pstn_info p ON p.CRDN_YR = c.CRDN_YR AND p.CRDN_NO = c.CRDN_NO AND p.DEL_YN = 'N'
-- 코드성 데이터 조인
LEFT JOIN tb_cd_detail impltTaskSe ON impltTaskSe.CD_GROUP_ID = 'IMPLT_TASK_SE_CD' AND impltTaskSe.CD_ID = ii.IMPLT_TASK_SE_CD
LEFT JOIN tb_cd_detail sggCd ON sggCd.CD_GROUP_ID = 'SGG_CD' AND sggCd.CD_ID = ii.SGG_CD
LEFT JOIN tb_cd_detail rgnSeCd ON rgnSeCd.CD_GROUP_ID = 'RGN_SE_CD' AND rgnSeCd.CD_ID = c.RGN_SE_CD
LEFT JOIN tb_cd_detail stdgEmdCd ON stdgEmdCd.CD_GROUP_ID = 'STDG_EMD_CD' AND stdgEmdCd.CD_ID = p.STDG_EMD_CD
WHERE ii.CRDN_YR = #{crdnYr}
AND ii.CRDN_NO = #{crdnNo}
AND ii.IMPLT_TASK_SE_CD = #{impltTaskSeCd}
AND ii.DEL_YN = 'N'
</select>
<!-- 이행정보 등록 -->
<insert id="insertImpltInfo" parameterType="CrdnImpltTaskVO">
INSERT INTO TB_IMPLT_INFO (
IMPLT_INFO_ID,
SGG_CD,
CRDN_YR,
CRDN_NO,
IMPLT_TASK_SE_CD,
IMPLT_BGNG_YMD,
IMPLT_END_YMD,
REG_DT,
RGTR,
DEL_YN
) VALUES (
#{impltInfoId},
#{sggCd},
#{crdnYr},
#{crdnNo},
#{impltTaskSeCd},
#{impltBgngYmd},
#{impltEndYmd},
NOW(),
#{rgtr},
'N'
)
</insert>
<!-- 이행정보 수정 -->
<update id="updateImpltInfo" parameterType="CrdnImpltTaskVO">
UPDATE TB_IMPLT_INFO
SET
IMPLT_BGNG_YMD = #{impltBgngYmd},
IMPLT_END_YMD = #{impltEndYmd},
MDFCN_DT = NOW(),
MDFR = #{mdfr}
WHERE IMPLT_INFO_ID = #{impltInfoId}
AND DEL_YN = 'N'
</update>
<!-- 이행정보 논리 삭제 -->
<update id="deleteImpltInfo" parameterType="CrdnImpltTaskVO">
UPDATE TB_IMPLT_INFO
SET
DEL_YN = 'Y',
DEL_DT = NOW(),
DLTR = #{dltr}
WHERE IMPLT_INFO_ID = #{impltInfoId}
</update>
<!-- 이행정보 존재 여부 확인 -->
<select id="selectImpltInfoExistsCount" parameterType="CrdnImpltTaskVO"
resultType="int">
SELECT COUNT(*)
FROM TB_IMPLT_INFO
WHERE CRDN_YR = #{crdnYr}
AND CRDN_NO = #{crdnNo}
AND IMPLT_TASK_SE_CD = #{impltTaskSeCd}
AND DEL_YN = 'N'
</select>
<!-- ==================== 이행 대상자 정보(TB_IMPLT_TRPR_INFO) 관련 쿼리 ==================== -->
<!-- 이행 대상자 정보 목록 조회 -->
<select id="selectImpltTrprInfoList" parameterType="CrdnImpltTaskVO"
resultType="CrdnImpltTrprInfoVO">
SELECT
iti.IMPLT_TRPR_INFO_ID,
iti.SGG_CD,
iti.IMPLT_INFO_ID,
iti.IMPLT_TRPR_FLNM,
iti.IMPLT_TRPR_ADDR,
iti.IMPLT_TRPR_DADDR,
iti.IMPLT_TRPR_ZIP,
iti.IMPLT_TRPR_SE_CD,
iti.OWNR_ACTR_INFO_ID,
iti.REG_DT,
iti.RGTR,
iti.MDFCN_DT,
iti.MDFR,
iti.DEL_YN,
iti.DEL_DT,
iti.DLTR,
-- 코드성 데이터 조인 (코드명)
impltTrprSe.CD_NM as IMPLT_TRPR_SE_CD_NM,
sggCd.CD_NM as SGG_CD_NM
FROM TB_IMPLT_TRPR_INFO iti
-- 코드성 데이터 조인
LEFT JOIN tb_cd_detail impltTrprSe ON impltTrprSe.CD_GROUP_ID = 'IMPLT_TRPR_SE_CD' AND impltTrprSe.CD_ID = iti.IMPLT_TRPR_SE_CD
LEFT JOIN tb_cd_detail sggCd ON sggCd.CD_GROUP_ID = 'SGG_CD' AND sggCd.CD_ID = iti.SGG_CD
WHERE iti.IMPLT_INFO_ID = #{impltInfoId}
AND iti.DEL_YN = 'N'
ORDER BY iti.IMPLT_TRPR_SE_CD, iti.IMPLT_TRPR_FLNM
</select>
<!-- 이행 대상자 정보 등록 -->
<insert id="insertImpltTrprInfo" parameterType="CrdnImpltTrprInfoVO">
INSERT INTO TB_IMPLT_TRPR_INFO (
IMPLT_TRPR_INFO_ID,
SGG_CD,
IMPLT_INFO_ID,
IMPLT_TRPR_FLNM,
IMPLT_TRPR_ADDR,
IMPLT_TRPR_DADDR,
IMPLT_TRPR_ZIP,
IMPLT_TRPR_SE_CD,
OWNR_ACTR_INFO_ID,
REG_DT,
RGTR,
DEL_YN
) VALUES (
#{impltTrprInfoId},
#{sggCd},
#{impltInfoId},
#{impltTrprFlnm},
#{impltTrprAddr},
#{impltTrprDaddr},
#{impltTrprZip},
#{impltTrprSeCd},
#{ownrActrInfoId},
NOW(),
#{rgtr},
'N'
)
</insert>
<!-- 이행 대상자 정보 물리 삭제 (이행정보 ID로) -->
<delete id="deleteImpltTrprInfoByImpltInfoId" parameterType="string">
DELETE FROM TB_IMPLT_TRPR_INFO
WHERE IMPLT_INFO_ID = #{impltInfoId}
</delete>
<!-- ==================== 소유자/행위자 정보 조회 쿼리 ==================== -->
<!-- 소유자 정보 목록 조회 (체크박스 선택용) -->
<select id="selectOwnrInfoList" parameterType="CrdnImpltTaskVO"
resultType="CrdnImpltTrprInfoVO">
SELECT
oi.OWNR_INFO_ID as ownrActrInfoId,
o.FLNM as impltTrprFlnm,
o.ADDR as impltTrprAddr,
o.DADDR as impltTrprDaddr,
o.ZIP as impltTrprZip,
o.LOTNO_MNO,
o.LOTNO_SNO,
'1' as impltTrprSeCd,
-- 코드성 데이터 조인 (코드명)
impltTrprSe.CD_NM as impltTrprSeCdNm,
false as selected,
aio.ACT_NO,
aio.ACT_TYPE_CD,
act.CD_NM AS ACT_TYPE_CD_NM
FROM tb_act_info aio
inner JOIN tb_ownr_info oi ON aio.CRDN_YR = oi.CRDN_YR and aio.CRDN_NO = oi.CRDN_NO and aio.DEL_YN = 'N'
INNER JOIN tb_ownr o ON o.OWNR_ID = oi.OWNR_ID AND o.DEL_YN = 'N'
LEFT JOIN tb_cd_detail impltTrprSe ON impltTrprSe.CD_GROUP_ID = 'IMPLT_TRPR_SE_CD' AND impltTrprSe.CD_ID = '1'
LEFT JOIN tb_cd_detail act ON act.CD_GROUP_ID = 'ACT_TYPE_CD' AND act.CD_ID = aio.ACT_TYPE_CD
WHERE aio.CRDN_YR = '2025'
AND aio.CRDN_NO = '000067'
AND aio.DEL_YN = 'N'
ORDER BY o.FLNM;
</select>
<!-- 행위자 정보 목록 조회 (체크박스 선택용) -->
<select id="selectActrInfoList" parameterType="CrdnImpltTaskVO"
resultType="CrdnImpltTrprInfoVO">
SELECT
ai.ACTR_INFO_ID as ownrActrInfoId,
o.FLNM as impltTrprFlnm,
o.ADDR as impltTrprAddr,
o.DADDR as impltTrprDaddr,
o.ZIP as impltTrprZip,
o.LOTNO_MNO,
o.LOTNO_SNO,
'2' as impltTrprSeCd,
-- 코드성 데이터 조인 (코드명)
impltTrprSe.CD_NM as impltTrprSeCdNm,
false as selected,
aio.ACT_NO,
aio.ACT_TYPE_CD,
act.CD_NM AS ACT_TYPE_CD_NM
FROM tb_act_info aio
INNER JOIN tb_actr_info ai ON ai.ACT_INFO_ID = aio.ACT_INFO_ID AND aio.DEL_YN = 'N'
INNER JOIN tb_ownr o ON o.OWNR_ID = ai.OWNR_ID AND o.DEL_YN = 'N'
LEFT JOIN tb_cd_detail impltTrprSe ON impltTrprSe.CD_GROUP_ID = 'IMPLT_TRPR_SE_CD' AND impltTrprSe.CD_ID = '2'
LEFT JOIN tb_cd_detail act ON act.CD_GROUP_ID = 'ACT_TYPE_CD' AND act.CD_ID = aio.ACT_TYPE_CD
WHERE aio.CRDN_YR = #{crdnYr}
AND aio.CRDN_NO = #{crdnNo}
AND aio.DEL_YN = 'N'
ORDER BY o.FLNM
</select>
<!-- ==================== 시퀀스 관련 쿼리 ==================== -->
<!-- 이행정보 ID를 위한 시퀀스 값 조회 (10자리 LPAD) -->
<select id="selectNextImpltInfoId" resultType="string">
SELECT LPAD(NEXTVAL(seq_implt_info_id), 10, '0')
</select>
<!-- 이행 대상자 정보 ID를 위한 시퀀스 값 조회 (10자리 LPAD) -->
<select id="selectNextImpltTrprInfoId" resultType="string">
SELECT LPAD(NEXTVAL(seq_implt_trpr_info_id), 10, '0')
</select>
</mapper>

@ -26,6 +26,12 @@
<definition name="*/*/*/*.base" extends="base">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}.jsp" />
</definition>
<definition name="*/*/*/*/*.base" extends="base">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}.jsp" />
</definition>
<definition name="*/*/*/*/*/*.base" extends="base">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}/{6}.jsp" />
</definition>
<!-- Popup Definition -->
<definition name="popup" template="/WEB-INF/views/layouts/popup/default.jsp">
@ -40,5 +46,11 @@
<definition name="*/*/*/*.popup" extends="popup">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}.jsp" />
</definition>
<definition name="*/*/*/*/*.popup" extends="popup">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}.jsp" />
</definition>
<definition name="*/*/*/*/*/*.popup" extends="popup">
<put-attribute name="main" value="/WEB-INF/views/{1}/{2}/{3}/{4}/{5}/{6}.jsp" />
</definition>
</tiles-definitions>

@ -18,60 +18,62 @@
<input type="hidden" id="pstnInfoId" name="pstnInfoId" value="${pstnInfoId}" />
<input type="hidden" id="actInfoId" name="actInfoId" value="${actInfoId}" />
<div class="popup_con">
<!-- 검색 영역 -->
<div class="box_column">
<div class="containers">
<div class="forms_table_non">
<form id="searchForm" name="searchForm">
<table>
<tr>
<th class="th">성명</th>
<td>
<input type="text" id="searchFlnm" name="searchFlnm" class="input" maxlength="100" placeholder="성명을 입력하세요"/>
</td>
<th class="th">주민등록번호</th>
<td>
<input type="text" id="searchRrno" name="searchRrno" class="input" maxlength="100" placeholder="주민등록번호를 입력하세요"/>
</td>
<td style="text-align: right; border: 0px;">
<button type="button" id="searchBtn" class="newbtn bg1">검색</button>
<button type="button" id="resetBtn" class="newbtn bg5" style="margin-left: 5px;">초기화</button>
</td>
</tr>
<tr>
<th class="th">이메일</th>
<td>
<input type="text" id="searchEml" name="searchEml" class="input" maxlength="50" placeholder="이메일을 입력하세요"/>
</td>
<th class="th">전화번호</th>
<td>
<input type="text" id="searchTelno" name="searchTelno" class="input" maxlength="11" placeholder="전화번호를 입력하세요"/>
</td>
<td style="text-align: right; border: 0px;">
<button type="button" id="newOwnrPopupBtn" class="newbtn bg1">${popupGubunName} 등록</button>
</td>
</tr>
</table>
</form>
<!-- 검색 영역 -->
<div class="box_column">
<div class="containers">
<div class="forms_table_non">
<form id="searchForm" name="searchForm">
<table>
<tr>
<th class="th">성명</th>
<td>
<input type="text" id="searchFlnm" name="searchFlnm" class="input" maxlength="100" placeholder="성명을 입력하세요"/>
</td>
<th class="th">주민등록번호</th>
<td>
<input type="text" id="searchRrno" name="searchRrno" class="input" maxlength="100" placeholder="주민등록번호를 입력하세요"/>
</td>
<td style="text-align: right; border: 0px;">
<button type="button" id="searchBtn" class="newbtn bg1">검색</button>
<button type="button" id="resetBtn" class="newbtn bg5" style="margin-left: 5px;">초기화</button>
</td>
</tr>
<tr>
<th class="th">이메일</th>
<td>
<input type="text" id="searchEml" name="searchEml" class="input" maxlength="50" placeholder="이메일을 입력하세요"/>
</td>
<th class="th">전화번호</th>
<td>
<input type="text" id="searchTelno" name="searchTelno" class="input" maxlength="11" placeholder="전화번호를 입력하세요"/>
</td>
<td style="text-align: right; border: 0px;">
<button type="button" id="newOwnrPopupBtn" class="newbtn bg1">${popupGubunName} 등록</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
</div>
<!-- 그리드 영역 -->
<div class="box_column" style="margin-top: 20px;">
<ul class="box_title">
<li class="tit">소유자 목록</li>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
</ul>
<div class="containers">
<div id="ownrSelectGrid"></div>
<!-- 그리드 영역 -->
<div class="box_column" style="margin-top: 20px;">
<ul class="box_title">
<li class="tit">소유자 목록</li>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
</ul>
<div class="containers">
<div id="ownrSelectGrid"></div>
</div>
</div>
</div>
</div>
<div class="popup_foot">
<button type="button" id="selectBtn" class="newbtns bg1">선택</button>
<button type="button" id="closeBtn" class="newbtns bg2">닫기</button>
<button type="button" id="selectBtn" class="newbtns bg1">선택</button>
<button type="button" id="closeBtn" class="newbtns bg2">닫기</button>
</div>
</div>
</div>

@ -0,0 +1,352 @@
<%@ 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" %>
<!-- 이행정보 관리 팝업 -->
<div class="popup_wrap">
<div class="popup_inner">
<div class="popup_tit">
<h2 class="tit" id="popupTitle">
<c:choose>
<c:when test="${mode eq 'U'}">이행정보 수정 - ${impltTaskSeCdNm}</c:when>
<c:when test="${mode eq 'V'}">이행정보 보기 - ${impltTaskSeCdNm}</c:when>
<c:otherwise>이행정보 등록 - ${impltTaskSeCdNm}</c:otherwise>
</c:choose>
</h2>
<a href="#" class="pop-x-btn modalclose" id="btnCloseTop"></a>
</div>
<div class="popup_con">
<div class="cash_tables">
<form id="impltTaskForm" name="impltTaskForm">
<input type="hidden" id="mode" name="mode" value="${mode}" />
<input type="hidden" id="impltInfoId" name="impltInfoId" value="${impltTaskInfo.impltInfoId}" />
<input type="hidden" id="crdnYr" name="crdnYr" value="${crdnYr}" />
<input type="hidden" id="crdnNo" name="crdnNo" value="${crdnNo}" />
<input type="hidden" id="impltTaskSeCd" name="impltTaskSeCd" value="${impltTaskSeCd}" />
<input type="hidden" id="sggCd" name="sggCd" value="00000" />
<table>
<colgroup>
<col style="width: 120px;" />
<col style="width: 120px;" />
<col style="width: 150px;" />
<col style="width: 180px;" />
<col style="width: 180px;" />
<col />
</colgroup>
<thead>
<tr>
<th>연도</th>
<th>단속번호</th>
<th>구분</th>
<th>단속동</th>
<th>행정처분시작일</th>
<th>행정처분종료일</th>
<th>행정처분간격일</th>
</tr>
</thead>
<tbody>
<tr>
<td>${crdnYr}</td>
<td>${crdnNo}</td>
<td>${crdnData.rgnSeCdNm}</td>
<td>${pstnData.stdgEmdCdNm}</td>
<td>
<input type="text" id="impltBgngYmd" name="impltBgngYmd" class="input calender datepicker"
style="width: 120px;" maxlength="10" validation-check="required"
value="${impltTaskInfo.impltBgngYmd}" onchange="calculateDaysBetween();" />
</td>
<td>
<input type="text" id="impltEndYmd" name="impltEndYmd" class="input calender datepicker"
style="width: 120px;" maxlength="10" validation-check="required"
value="${impltTaskInfo.impltEndYmd}" onchange="calculateDaysBetween();" />
</td>
<td>
<input type="text" id="impltDaysCnt" name="impltDaysCnt" class="input"
style="width: 100px; text-align: right" readonly value="${impltTaskInfo.impltDaysCnt}" />
일 (자동계산)
</td>
</tr>
</tbody>
</table>
</form>
</div>
<!-- 그리드 영역 -->
<div class="box_column" style="margin-top: 20px;">
<ul class="box_title">
<li class="tit">소유자 목록</li>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
</ul>
<div class="containers">
<div id="grid"></div>
</div>
</div>
</div>
<!-- 팝업 버튼 -->
<div class="popup_foot">
<c:if test="${mode ne 'V'}">
<button type="button" id="btnSave" class="newbtns bg1">저장</button>
</c:if>
<button type="button" class="newbtns bg2 modalclose" id="btnClose">닫기</button>
</div>
</div>
</div>
<script type="text/javascript">
/**
* 이행정보 관리 팝업 스크립트
* 중요한 로직 주석: datepicker 적용, 자동 간격일 계산, 소유자/행위자 선택 기능 구현
*/
(function(window, $) {
'use strict';
var ImpltTaskPopup = {
// 팝업 모드 및 기본 정보
mode: '${mode}',
crdnYr: '${crdnYr}',
crdnNo: '${crdnNo}',
impltTaskSeCd: '${impltTaskSeCd}',
impltTaskSeCdNm: '${impltTaskSeCdNm}',
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
/**
* 그리드 설정 초기화
* @returns {Object} 그리드 설정 객체
*/
initConfig: function() {
// 데이터 소스 설정
var dataSource = this.createDataSource();
// 그리드 설정 객체 생성
var gridConfig = new XitTuiGridConfig();
// 기본 설정
gridConfig.setOptDataSource(dataSource); // 데이터소스 연결
gridConfig.setOptGridId('grid'); // 그리드를 출력할 Element ID
gridConfig.setOptGridHeight(200); // 그리드 높이(단위: px)
gridConfig.setOptRowHeight(30); // 그리드 행 높이(단위: px)
gridConfig.setOptUseClientSort(true); // 클라이언트 사이드 정렬
gridConfig.setOptRowHeaderType('checkbox');
gridConfig.setOptColumns(this.getGridColumns());
return gridConfig;
},
/**
* 그리드 컬럼 정의
* @returns {Array} 그리드 컬럼 배열
*/
getGridColumns: function() {
var self = this;
return [
{header: '행위번호', name: 'actNo', align: 'center', width: 60},
{header: '행위구분', name: 'actTypeCdNm', align: 'center', width: 120},
{header: '소유자, 행위자 구분', name: 'impltTrprSeCdNm', align: 'center', width: 120},
{header: '성명', name: 'impltTrprFlnm', align: 'center', width: 120},
{header: '우편번호', name: 'impltTrprZip', align: 'center', width: 80},
{header: '주소', name: 'impltTrprAddr', align: 'left', width: 300},
{header: '상세주소', name: 'impltTrprDaddr', align: 'left', width: 300 },
{ header: '지번', name: 'lotnoMSno', align: 'center', width: 90, formatter: function(row, col, cellValue, item, rowIndex) {
var mno = row.row.lotnoMno || '';
var sno = row.row.lotnoSno || '';
return mno + (sno ? ' - ' + sno : '');
}
},
{header: '지번 본번', name: 'lotnoMno', align: 'right', width: 90, hidden: true},
{header: '지번 부번', name: 'lotnoSno', align: 'right', width: 90, hidden: true},
{ header: '소유자 행위자 정보 ID', name: 'ownrActrInfoId', align: 'center', width: 120, hidden: true },
{ header: '이행 대상자 정보 ID', name: 'impltTrprInfoId', align: 'center', width: 120, hidden: true }
];
},
/**
* 데이터 소스 생성
* @returns {Object} 데이터 소스 설정 객체
*/
createDataSource: function() {
return {
api: {
readData: {
url: '<c:url value="/crdn/crndRegistAndView/crdnImpltTask/selectOwnrActrInfoList.ajax"/>',
method: 'GET',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: false, // 초기 데이터 요청 여부
serializer: function(params) {
var defaultParams = $.param(params);
var extra = $.param({ "crdnYr": ImpltTaskPopup.crdnYr, "crdnNo": ImpltTaskPopup.crdnNo });
return defaultParams + '&' + extra;
}
};
},
/**
* 그리드 인스턴스 생성
*/
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 && responseObj.data ){
// 총 개수 업데이트
$('#totalCount').text('총 ' + responseObj.data.contents.length + '건');
}
});
},
},
/**
* 초기화
*/
init: function() {
// 그리드 생성
this.grid.create();
// 이벤트 핸들러 설정
this.eventBindEvents();
// 그리드 조회
this.grid.instance.readData();
},
/**
* 이벤트 초기화
*/
eventBindEvents: function() {
var self = this;
$('.datepicker').datepicker({
container: '.popup_inner',
language: "kr"
});
// 저장 버튼
$('#btnSave').on('click', function() {
self.save();
});
},
/**
* 저장
*/
save: function() {
var self = this;
// 유효성 검증
if (!this.validateForm()) {
return;
}
var formData = ;
var url = this.mode === 'C' ?
'<c:url value="/crdn/crndRegistAndView/crdnImpltTask/insertImpltInfo.ajax"/>' :
'<c:url value="/crdn/crndRegistAndView/crdnImpltTask/updateImpltInfo.ajax"/>';
$.ajax({
url: url,
type: 'POST',
data: formData,
success: function(response) {
if (response && response.success) {
alert(response.message || '이행정보가 성공적으로 저장되었습니다.');
if (window.opener && window.opener.refreshCrdnList) {
window.opener.refreshCrdnList();
}
window.close();
} else {
alert(response.message || '저장 중 오류가 발생했습니다.');
}
}
});
},
/**
* 폼 유효성 검증
*/
validateForm: function() {
if (!$('#impltBgngYmd').val()) {
alert('행정처분 시작일을 입력해주세요.');
$('#impltBgngYmd').focus();
return false;
}
if (!$('#impltEndYmd').val()) {
alert('행정처분 종료일을 입력해주세요.');
$('#impltEndYmd').focus();
return false;
}
if ($('#impltBgngYmd').val() > $('#impltEndYmd').val()) {
alert('행정처분 시작일은 종료일보다 이전이어야 합니다.');
$('#impltBgngYmd').focus();
return false;
}
return true;
},
};
/**
* 행정처분 간격일 자동 계산 함수
* 중요한 로직 주석: 시작일과 종료일 입력시 자동으로 간격일을 계산한다.
*/
window.calculateDaysBetween = function() {
var startDate = $('#impltBgngYmd').inputmask("unmaskedvalue");
var endDate = $('#impltEndYmd').inputmask("unmaskedvalue");
if (startDate && endDate && startDate.length === 8 && endDate.length === 8) {
var start = new Date(startDate.substring(0,4),
parseInt(startDate.substring(4,6)) - 1,
startDate.substring(6,8));
var end = new Date(endDate.substring(0,4),
parseInt(endDate.substring(4,6)) - 1,
endDate.substring(6,8));
var timeDiff = end.getTime() - start.getTime();
var daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
$('#impltDaysCnt').val(daysDiff);
}
};
// 초기화 실행
$(document).ready(function() {
ImpltTaskPopup.init();
});
})(window, jQuery);
</script>

@ -445,6 +445,41 @@
window.open(url, 'crdnRegistAndViewView', 'width=800,height=700,scrollbars=yes');
},
/**
* 이행정보 관리 팝업을 엽니다.
*
* @param crdnYr 단속 연도
* @param crdnNo 단속 번호
* @param impltTaskSeCd 이행업무구분코드 (1:처분사전, 2:시정명령, 3:시정촉구, 4:부과예고, 5:부과, 6:납부촉구)
*/
openImpltTaskPopup: function(crdnYr, crdnNo, impltTaskSeCd) {
// 기존 이행정보가 있는지 확인하여 모드 결정
var self = this;
$.ajax({
url: '<c:url value="/crdn/crndRegistAndView/crdnImpltTask/selectImpltInfo.ajax"/>',
type: 'GET',
data: {
crdnYr: crdnYr,
crdnNo: crdnNo,
impltTaskSeCd: impltTaskSeCd
},
success: function(response) {
var mode = (response && response.success && response.data) ? 'U' : 'C';
var w = 1200, h = 800;
var left = Math.max(0, (window.screen.availLeft + (window.screen.availWidth - w) / 2));
var top = Math.max(0, (window.screen.availTop + (window.screen.availHeight - h) / 2));
var url = '<c:url value="/crdn/crndRegistAndView/crdnImpltTask/impltTaskPopup.do"/>?mode=' + mode +
'&crdnYr=' + encodeURIComponent(crdnYr) +
'&crdnNo=' + encodeURIComponent(crdnNo) +
'&impltTaskSeCd=' + encodeURIComponent(impltTaskSeCd);
window.open(url, 'impltTaskPopup', 'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top + ',scrollbars=yes,resizable=yes');
}
});
},
/**
* 목록 현재 페이징 새로고침
*/
@ -557,9 +592,16 @@
self.openViewPopup(self.selectedRow.crdnYr, self.selectedRow.crdnNo);
});
// 상태 업데이트 버튼 클릭 이벤트들
// 이행정보 관리 팝업 호출 이벤트들 - "처분사전" 버튼
$("#btnDsps").on('click', function() {
self.updateStatus('20', '처분사전');
// 선택된 행 확인
if (!self.selectedRow) {
alert('이행정보를 관리할 단속 건을 선택해주세요.');
return;
}
// 처분사전 팝업 열기
self.openImpltTaskPopup(self.selectedRow.crdnYr, self.selectedRow.crdnNo, '1'); // 1: 처분사전
});
$("#btnCrcCmd").on('click', function() {

Loading…
Cancel
Save