단속 > 단속 등록&열람: 소유자정보 관리 기능 추가, 목록 조회 API 및 페이징 처리 구현, Grid 컬럼 정의 및 JSP 연동, MyBatis 매퍼 연계

dev
박성영 4 months ago
parent 720445c5c0
commit df558c79d9

@ -0,0 +1,51 @@
package go.kr.project.crdn.crndRegistAndView.crdnOwnr.controller;
import egovframework.util.ApiResponseUtil;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.model.CrdnOwnrVO;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.service.CrdnOwnrService;
import io.swagger.v3.oas.annotations.Operation;
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.web.bind.annotation.*;
import java.util.List;
/**
*
* : API . (setTotalCountsetPagingYn) .
*/
@Controller
@RequestMapping("/crdn/crndRegistAndView/crdnOwnr")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "단속 소유자정보", description = "단속 자료 소유자정보 API")
public class CrdnOwnrController {
private final CrdnOwnrService service;
/**
* (AJAX)
*/
@Operation(summary = "소유자정보 목록 조회", description = "단속연도/번호로 소유자정보를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "400", description = "조회 실패")
})
@PostMapping("/list.ajax")
public ResponseEntity<?> listAjax(@ModelAttribute CrdnOwnrVO vo) {
// 1. 총 개수 조회
int totalCount = service.selectOwnrInfoListTotalCount(vo);
// 2. 응답 데이터 구성
vo.setTotalCount(totalCount);
// 3. 페이징 처리 (가이드 준수)
vo.setPagingYn("Y");
List<CrdnOwnrVO> list = service.selectOwnrInfoList(vo);
return ApiResponseUtil.successWithGrid(list, vo);
}
}

@ -0,0 +1,33 @@
package go.kr.project.crdn.crndRegistAndView.crdnOwnr.mapper;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.model.CrdnOwnrVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* MyBatis
* : tb_ownr_info CRUD / .
*/
@Mapper
public interface CrdnOwnrMapper {
/** 목록 조회 */
List<CrdnOwnrVO> selectOwnrInfoList(CrdnOwnrVO vo);
/** 총 개수 */
int selectOwnrInfoListTotalCount(CrdnOwnrVO vo);
/** 등록 */
int insertOwnrInfo(CrdnOwnrVO vo);
/** 상세 조회 */
CrdnOwnrVO selectOwnrInfoByPk(String ownrInfoId);
/** 수정 */
int updateOwnrInfo(CrdnOwnrVO vo);
/** 삭제 (논리삭제) */
int deleteOwnrInfo(@Param("ownrInfoId") String ownrInfoId, @Param("dltr") String dltr);
}

@ -0,0 +1,46 @@
package go.kr.project.crdn.crndRegistAndView.crdnOwnr.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;
/**
* VO
* : tb_ownr_info , .
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CrdnOwnrVO extends PagingVO {
// ============ 테이블 컬럼 ============
private String ownrInfoId; // 소유자 정보 ID
private String sggCd; // 시군구 코드
private String crdnYr; // 단속 연도
private String crdnNo; // 단속 번호
private String pstnInfoId; // 위치 정보 ID
private String ownrId; // 소유자 ID
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDt; // 등록 일시
private String rgtr; // 등록자
private String delYn; // 삭제 여부
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime delDt; // 삭제 일시
private String dltr; // 삭제자
// ============ 조인 컬럼(사용자정보) ============
private String rgtrAcnt; // 등록자 계정
private String rgtrNm; // 등록자명
private String dltrAcnt; // 삭제자 계정
private String dltrNm; // 삭제자명
}

@ -0,0 +1,23 @@
package go.kr.project.crdn.crndRegistAndView.crdnOwnr.service;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.model.CrdnOwnrVO;
import java.util.List;
/**
*
*/
public interface CrdnOwnrService {
List<CrdnOwnrVO> selectOwnrInfoList(CrdnOwnrVO vo);
int selectOwnrInfoListTotalCount(CrdnOwnrVO vo);
int insertOwnrInfo(CrdnOwnrVO vo);
// 중요로직: 상세 조회 - 그리드 더블클릭 시 사용
CrdnOwnrVO selectOwnrInfoByPk(String ownrInfoId);
// 중요로직: 수정 - 팝업에서 수정 저장 시 사용
int updateOwnrInfo(CrdnOwnrVO vo);
// 중요로직: 다중 삭제 - 체크박스로 선택된 소유자정보들을 논리 삭제
int deleteOwnrInfos(List<String> ownrInfoIds, String dltr);
}

@ -0,0 +1,78 @@
package go.kr.project.crdn.crndRegistAndView.crdnOwnr.service.impl;
import egovframework.exception.MessageException;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.mapper.CrdnOwnrMapper;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.model.CrdnOwnrVO;
import go.kr.project.crdn.crndRegistAndView.crdnOwnr.service.CrdnOwnrService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
*
* : - , .
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CrdnOwnrServiceImpl extends EgovAbstractServiceImpl implements CrdnOwnrService {
private final CrdnOwnrMapper mapper;
@Override
public List<CrdnOwnrVO> selectOwnrInfoList(CrdnOwnrVO vo) {
return mapper.selectOwnrInfoList(vo);
}
@Override
public int selectOwnrInfoListTotalCount(CrdnOwnrVO vo) {
return mapper.selectOwnrInfoListTotalCount(vo);
}
@Override
public int insertOwnrInfo(CrdnOwnrVO vo) {
CrdnOwnrVO existingRecord = mapper.selectOwnrInfoList(CrdnOwnrVO.builder()
.crdnYr(vo.getCrdnYr())
.crdnNo(vo.getCrdnNo())
.pstnInfoId(vo.getPstnInfoId())
.ownrId(vo.getOwnrId())
.build()).stream().findFirst().orElse(null);
if (existingRecord != null) {
throw new MessageException("이미 등록된 소유자정보가 있습니다.");
}
return mapper.insertOwnrInfo(vo);
}
@Override
public CrdnOwnrVO selectOwnrInfoByPk(String ownrInfoId) {
return mapper.selectOwnrInfoByPk(ownrInfoId);
}
@Override
public int updateOwnrInfo(CrdnOwnrVO vo) {
return mapper.updateOwnrInfo(vo);
}
@Override
public int deleteOwnrInfos(List<String> ownrInfoIds, String dltr) {
if (ownrInfoIds == null || ownrInfoIds.isEmpty()) {
return 0;
}
// 중요로직: 각 소유자정보 ID에 대해 논리 삭제 수행
int deletedCount = 0;
for (String ownrInfoId : ownrInfoIds) {
if (ownrInfoId != null && !ownrInfoId.trim().isEmpty()) {
int result = mapper.deleteOwnrInfo(ownrInfoId, dltr);
deletedCount += result;
}
}
return deletedCount;
}
}

@ -0,0 +1,131 @@
<?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.crdnOwnr.mapper.CrdnOwnrMapper">
<!-- 소유자정보 목록 조회 -->
<select id="selectOwnrInfoList" parameterType="CrdnOwnrVO"
resultType="CrdnOwnrVO">
/* OwnrInfoMapper.selectOwnrInfoList : 소유자정보 목록 조회 */
SELECT
o.OWNR_INFO_ID,
o.SGG_CD,
o.CRDN_YR,
o.CRDN_NO,
o.PSTN_INFO_ID,
o.OWNR_ID,
o.REG_DT,
o.RGTR,
regUser.USER_ACNT AS RGTR_ACNT,
regUser.USER_NM AS RGTR_NM,
o.DEL_YN,
o.DEL_DT,
o.DLTR,
delUser.USER_ACNT AS DLTR_ACNT,
delUser.USER_NM AS DLTR_NM
FROM tb_ownr_info o
LEFT JOIN tb_user regUser ON regUser.USER_ID = o.RGTR
LEFT JOIN tb_user delUser ON delUser.USER_ID = o.DLTR
WHERE o.DEL_YN = 'N'
<if test='crdnYr != null and crdnYr != ""'>
AND o.CRDN_YR = #{crdnYr}
</if>
<if test='crdnNo != null and crdnNo != ""'>
AND o.CRDN_NO = #{crdnNo}
</if>
ORDER BY o.REG_DT DESC, o.OWNR_INFO_ID DESC
<if test='pagingYn != null and pagingYn == "Y"'>
limit #{startIndex}, #{perPage} /* 서버사이드 페이징 처리 */
</if>
</select>
<!-- 소유자정보 총 개수 조회 -->
<select id="selectOwnrInfoListTotalCount" parameterType="CrdnOwnrVO" resultType="int">
/* OwnrInfoMapper.selectOwnrInfoListTotalCount : 소유자정보 총 개수 조회 */
SELECT COUNT(*)
FROM tb_ownr_info o
WHERE o.DEL_YN = 'N'
<if test='crdnYr != null and crdnYr != ""'>
AND o.CRDN_YR = #{crdnYr}
</if>
<if test='crdnNo != null and crdnNo != ""'>
AND o.CRDN_NO = #{crdnNo}
</if>
</select>
<!-- 소유자정보 등록 (시컨스 사용) - 필요시 사용 -->
<insert id="insertOwnrInfo" parameterType="CrdnOwnrVO">
/* OwnrInfoMapper.insertOwnrInfo : 소유자정보 등록 */
INSERT INTO tb_ownr_info (
OWNR_INFO_ID,
SGG_CD,
CRDN_YR,
CRDN_NO,
PSTN_INFO_ID,
OWNR_ID,
REG_DT,
RGTR,
DEL_YN
) VALUES (
LPAD(NEXTVAL(seq_ownr_info_id), 10, '0'),
#{sggCd},
#{crdnYr},
#{crdnNo},
#{pstnInfoId},
#{ownrId},
NOW(),
#{rgtr},
'N'
)
</insert>
<!-- 소유자정보 상세 조회 - 필요시 사용 -->
<select id="selectOwnrInfoByPk" parameterType="String" resultType="CrdnOwnrVO">
/* OwnrInfoMapper.selectOwnrInfoByPk : 소유자정보 상세 조회 */
SELECT
o.OWNR_INFO_ID,
o.SGG_CD,
o.CRDN_YR,
o.CRDN_NO,
o.PSTN_INFO_ID,
o.OWNR_ID,
o.REG_DT,
o.RGTR,
regUser.USER_ACNT AS RGTR_ACNT,
regUser.USER_NM AS RGTR_NM,
o.DEL_YN,
o.DEL_DT,
o.DLTR,
delUser.USER_ACNT AS DLTR_ACNT,
delUser.USER_NM AS DLTR_NM
FROM tb_ownr_info o
LEFT JOIN tb_user regUser ON regUser.USER_ID = o.RGTR
LEFT JOIN tb_user delUser ON delUser.USER_ID = o.DLTR
WHERE o.OWNR_INFO_ID = #{ownrInfoId}
AND o.DEL_YN = 'N'
</select>
<!-- 소유자정보 수정 - 필요시 사용 -->
<update id="updateOwnrInfo" parameterType="CrdnOwnrVO">
/* OwnrInfoMapper.updateOwnrInfo : 소유자정보 수정 */
UPDATE tb_ownr_info
SET
PSTN_INFO_ID = #{pstnInfoId},
OWNR_ID = #{ownrId}
WHERE OWNR_INFO_ID = #{ownrInfoId}
AND DEL_YN = 'N'
</update>
<!-- 소유자정보 삭제 (논리삭제) - 필요시 사용 -->
<update id="deleteOwnrInfo">
/* OwnrInfoMapper.deleteOwnrInfo : 소유자정보 논리 삭제 */
UPDATE tb_ownr_info
SET
DEL_YN = 'Y',
DEL_DT = NOW(),
DLTR = #{dltr}
WHERE OWNR_INFO_ID = #{ownrInfoId}
AND DEL_YN = 'N'
</update>
</mapper>

@ -149,6 +149,9 @@
</form>
</div>
</div>
<%-- 소유자 TUI GRID --%>
<div class="popup_foot">
<div class="mainccs rig">
<button type="button" id="saveBtn" class="newbtn bg1">저장</button>

@ -0,0 +1,187 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<script type="text/javascript">
/**
* 소유자 정보 그리드 모듈 (OwnrInfo)
* 단속 데이터의 소유자 관련 정보를 표시합니다.
*/
(function(window, $) {
'use strict';
/**
* OwnrInfo (소유자 정보) 관리 네임스페이스
*/
var CrdnDetailViewOwnrInfo = {
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
/**
* 그리드 설정 초기화
* @returns {Object} 그리드 설정 객체
*/
initConfig: function() {
// 데이터 소스 설정
var dataSource = this.createDataSource();
// 그리드 설정 객체 생성
var gridConfig = new XitTuiGridConfig();
// 기본 설정
gridConfig.setOptDataSource(dataSource); // 데이터소스 연결
gridConfig.setOptGridId('ownrInfo'); // 그리드를 출력할 Element ID
gridConfig.setOptGridHeight(200); // 그리드 높이(단위: px)
gridConfig.setOptRowHeight(30); // 그리드 행 높이(단위: px)
gridConfig.setOptRowHeaderType('checkbox'); // 행 첫번째 셀 타입(checkbox: 체크박스)
gridConfig.setOptUseClientSort(true); // 클라이언트 사이드 정렬
gridConfig.setOptColumns(this.getGridColumns());
return gridConfig;
},
/**
* 그리드 컬럼 정의
* @returns {Array} 그리드 컬럼 배열
*/
getGridColumns: function() {
return [
{ header: '소유자정보ID', name: 'ownrInfoId', align: 'center', width: 110, hidden: true },
{ header: '시군구코드', name: 'sggCd', align: 'center', width: 80, hidden: true },
{ header: '단속연도', name: 'crdnYr', align: 'center', width: 80, sortable: true },
{ header: '단속번호', name: 'crdnNo', align: 'center', width: 90, sortable: true },
{ header: '위치정보ID', name: 'pstnInfoId', align: 'center', width: 110, hidden: true },
{ header: '소유자ID', name: 'ownrId', align: 'center', width: 110, hidden: true },
{ header: '등록일시', name: 'regDt', align: 'center', width: 150,
formatter: function (e) {
return e.value ? moment(e.value).format('YYYY-MM-DD HH:mm') : '';
}
},
{ header: '등록자', name: 'rgtr', align: 'center', width: 100 },
{ header: '삭제여부', name: 'delYn', align: 'center', width: 80, hidden: true },
{ header: '삭제일시', name: 'delDt', align: 'center', width: 150, hidden: true,
formatter: function (e) {
return e.value ? moment(e.value).format('YYYY-MM-DD HH:mm') : '';
}
},
{ header: '삭제자', name: 'dltr', align: 'center', width: 100, hidden: true }
];
},
/**
* 데이터 소스 생성
* @returns {Object} 데이터 소스 설정 객체
*/
createDataSource: function() {
return {
api: {
readData: {
url: '<c:url value="/crdn/crndRegistAndView/crdnOwnr/list.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: true, // 초기 데이터 요청 여부
serializer: function(params) {
// 기본 파라미터 (페이지 정보 등)
var defaultParams = $.param(params);
// detailView 화면의 hidden 입력값에서 단속연도/번호를 가져온다.
var crdnYr = $('#crdnYr').val();
var crdnNo = $('#crdnNo').val();
var extra = $.param({ crdnYr: crdnYr, crdnNo: 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) {
});
// 행 클릭 이벤트
this.instance.on('click', function(ev) {
if (ev.rowKey !== undefined && ev.rowKey !== null) {
var rowData = self.instance.getRow(ev.rowKey);
console.log('OwnrInfo 행 클릭:', rowData);
}
});
// 행 더블클릭 이벤트
this.instance.on('dblclick', function(ev) {
var rowKey = ev.rowKey;
var rowData = self.instance.getRow(rowKey);
if (rowData) {
console.log('OwnrInfo 행 더블클릭:', rowData);
// 소유자 상세 정보 모달 또는 추가 동작 구현 가능
}
});
}
},
/**
* 검색 함수
* 메인 모듈에서 호출됨
*/
search: function() {
if (this.grid.instance) {
this.grid.instance.readData();
console.log('OwnrInfo 검색 실행');
}
},
/**
* 모듈 초기화
*/
init: function() {
// 그리드 생성
this.grid.create();
// 메인 모듈에 자신을 등록
if (window.CrdnDetailView) {
window.CrdnDetailView.registerGrid('ownrInfo', this);
}
console.log('OwnrInfo (소유자 정보) 모듈이 초기화되었습니다.');
}
};
// DOM 준비 완료 시 초기화
$(document).ready(function() {
// 메인 모듈이 로드될 때까지 약간의 지연
setTimeout(function() {
CrdnDetailViewOwnrInfo.init();
}, 150);
});
// 전역 네임스페이스에 모듈 노출
window.CrdnDetailViewOwnrInfo = CrdnDetailViewOwnrInfo;
})(window, jQuery);
</script>

@ -120,7 +120,7 @@
<li class="tit">소유자 정보</li>
</ul>
<div class="containers">
<div id="grid2"></div>
<div id="ownrInfo"></div>
</div>
</div>
</div>
@ -155,7 +155,7 @@
<!-- 각 그리드별 개별 JSP 포함 -->
<%@ include file="crdnPstnInfo/detailView-pstn.jsp" %>
<%@ include file="detailView-grid2.jsp" %>
<%@ include file="detailView-ownrInfo.jsp" %>
<%@ include file="detailView-grid3.jsp" %>
<%@ include file="detailView-grid4.jsp" %>
@ -199,7 +199,7 @@
grids: {
// 각 그리드 모듈 인스턴스들
pstn: null,
grid2: null,
ownrInfo: null,
grid3: null,
grid4: null
},
@ -328,8 +328,8 @@
if (this.grids.pstn && this.grids.pstn.search) {
this.grids.pstn.search();
}
if (this.grids.grid2 && this.grids.grid2.search) {
this.grids.grid2.search();
if (this.grids.ownrInfo && this.grids.ownrInfo.search) {
this.grids.ownrInfo.search();
}
if (this.grids.grid3 && this.grids.grid3.search) {
this.grids.grid3.search();

Loading…
Cancel
Save