단속 > 단속 등록&열람: 법규/구조/용도 지수 필수 입력값 검증 추가, 등록번호 시퀀스 적용 및 중복 등록 방지 로직 제거, 등록/수정 로직 개선, UI 및 VO 업데이트

dev
박성영 4 months ago
parent c04ca3c631
commit b471ef0afe

@ -0,0 +1,7 @@
CREATE SEQUENCE seq_act_info_id
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9999999999
CACHE 1000
NOCYCLE;

@ -58,7 +58,9 @@ public class CrdnActInfoVO extends PagingVO {
private String sggCdNm; // 시군구 코드명
private String actTypeCdNm; // 행위 유형 코드명
private String vltnLwrgCd1Nm; // 위반 법규 코드 1명
private String vltnLaw1; // 위반 법 1
private String vltnLwrgCd2Nm; // 위반 법규 코드 2명
private String vltnLaw2; // 위반 법 2
private String strctIdxCdNm; // 구조 지수 코드명
private String usgIdxCdNm; // 용도 지수 코드명

@ -54,27 +54,7 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
@Override
public int insertActInfo(CrdnActInfoVO vo) {
log.debug("불법위반행위정보 등록: {}", vo);
// 중요한 로직 주석: 동일한 단속년도/단속번호/위치정보에 대한 중복 등록 방지
CrdnActInfoVO existingRecord = null;
if (vo.getCrdnYr() != null && vo.getCrdnNo() != null && vo.getPstnInfoId() != null) {
List<CrdnActInfoVO> existingList = mapper.selectActInfoList(CrdnActInfoVO.builder()
.crdnYr(vo.getCrdnYr())
.crdnNo(vo.getCrdnNo())
.pstnInfoId(vo.getPstnInfoId())
.build());
if (!existingList.isEmpty()) {
existingRecord = existingList.get(0);
}
}
if (existingRecord != null) {
log.warn("이미 등록된 불법위반행위정보가 존재합니다. 단속년도: {}, 단속번호: {}, 위치정보ID: {}",
vo.getCrdnYr(), vo.getCrdnNo(), vo.getPstnInfoId());
return 0;
}
return mapper.insertActInfo(vo);
}

@ -19,8 +19,10 @@
act.CD_NM AS ACT_TYPE_CD_NM,
a.VLTN_LWRG_CD_1,
vltn1.VLTN_LWRG_NM AS VLTN_LWRG_CD_1_NM,
vltn1.VLTN_LAW AS VLTN_LAW_1,
a.VLTN_LWRG_CD_2,
vltn2.VLTN_LWRG_NM AS VLTN_LWRG_CD_2_NM,
vltn2.VLTN_LAW AS VLTN_LAW_2,
a.STRCT_IDX_CD,
strct.STRCT_NM AS STRCT_IDX_CD_NM,
a.USG_IDX_CD,
@ -107,12 +109,12 @@
RGTR,
DEL_YN
) VALUES (
CONCAT('ACTI', LPAD(NEXTVAL(seq_act_info_id), 16, '0')),
LPAD(NEXTVAL(seq_act_info_id), 10, '0'),
#{sggCd},
#{crdnYr},
#{crdnNo},
#{pstnInfoId},
#{actBgngYmd},
replace(#{actBgngYmd},'-',''),
#{actTypeCd},
#{vltnLwrgCd1},
#{vltnLwrgCd2},
@ -144,8 +146,10 @@
act.CD_NM AS ACT_TYPE_CD_NM,
a.VLTN_LWRG_CD_1,
vltn1.VLTN_LWRG_NM AS VLTN_LWRG_CD_1_NM,
vltn1.VLTN_LAW AS VLTN_LAW_1,
a.VLTN_LWRG_CD_2,
vltn2.VLTN_LWRG_NM AS VLTN_LWRG_CD_2_NM,
vltn2.VLTN_LAW AS VLTN_LAW_2,
a.STRCT_IDX_CD,
strct.STRCT_NM AS STRCT_IDX_CD_NM,
a.USG_IDX_CD,
@ -183,7 +187,7 @@
<update id="updateActInfo" parameterType="CrdnActInfoVO">
/* ActInfoMapper.updateActInfo : 불법위반행위정보 수정 */
UPDATE tb_act_info SET
ACT_BGNG_YMD = #{actBgngYmd},
ACT_BGNG_YMD = replace(#{actBgngYmd},'-',''),
ACT_TYPE_CD = #{actTypeCd},
VLTN_LWRG_CD_1 = #{vltnLwrgCd1},
VLTN_LWRG_CD_2 = #{vltnLwrgCd2},

@ -48,9 +48,9 @@
</td>
</tr>
<tr>
<th class="th">위반법규1</th>
<th class="th"><span class="required">*</span> 위반법규1</th>
<td colspan="3">
<input type="text" id="vltnLwrgNm1" name="vltnLwrgNm1" class="input" style="width: 250px;" placeholder="위반법규를 입력하거나 선택하세요" autocomplete="off" value="${data.vltnLwrgCd1Nm}"/>
<input type="text" id="vltnLwrgNm1" name="vltnLwrgNm1" class="input" style="width: 250px;" placeholder="위반법규를 입력하거나 선택하세요" autocomplete="off" value="${data.vltnLwrgCd1Nm}" validation-check="required"/>
<input type="hidden" id="vltnLwrgCd1" name="vltnLwrgCd1" value="${data.vltnLwrgCd1}"/>
</td>
<%--<th class="th">위반법규2</th>
@ -60,35 +60,35 @@
</td>--%>
</tr>
<tr>
<th class="th">구조지수</th>
<th class="th"><span class="required">*</span> 구조지수</th>
<td>
<input type="text" id="strctIdxNm" name="strctIdxNm" class="input" style="width: 250px;" placeholder="구조명을 입력하거나 선택하세요" autocomplete="off" value="${data.strctIdxCdNm}"/>
<input type="text" id="strctIdxNm" name="strctIdxNm" class="input" style="width: 250px;" placeholder="구조명을 입력하거나 선택하세요" autocomplete="off" value="${data.strctIdxCdNm}" validation-check="required"/>
<input type="hidden" id="strctIdxCd" name="strctIdxCd" value="${data.strctIdxCd}"/>
</td>
<th class="th">용도지수</th>
<th class="th"><span class="required">*</span> 용도지수</th>
<td>
<input type="text" id="usgNm" name="usgNm" class="input" style="width: 250px;" placeholder="용도명을 입력하거나 선택하세요" autocomplete="off" value="${data.usgIdxCdNm}"/>
<input type="text" id="usgNm" name="usgNm" class="input" style="width: 250px;" placeholder="용도명을 입력하거나 선택하세요" autocomplete="off" value="${data.usgIdxCdNm}" validation-check="required"/>
<input type="hidden" id="usgIdxCd" name="usgIdxCd" value="${data.usgIdxCd}"/>
</td>
</tr>
<tr>
<th class="th">높이(m)</th>
<th class="th"><span class="required">*</span> 높이(m)</th>
<td>
<input type="text" id="hgt" name="hgt" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 10.50" validation-check="number" value="${data.hgt}"/>
<input type="text" id="hgt" name="hgt" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 10.50" validation-check="required number" value="${data.hgt}"/>
</td>
<th class="th">가로(m)</th>
<th class="th"><span class="required">*</span> 가로(m)</th>
<td>
<input type="text" id="wdth" name="wdth" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 5.25" validation-check="number" value="${data.wdth}"/>
<input type="text" id="wdth" name="wdth" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 5.25" validation-check="required number" value="${data.wdth}"/>
</td>
</tr>
<tr>
<th class="th">세로(m)</th>
<th class="th"><span class="required">*</span> 세로(m)</th>
<td>
<input type="text" id="vrtc" name="vrtc" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 8.75" validation-check="number" value="${data.vrtc}"/>
<input type="text" id="vrtc" name="vrtc" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 8.75" validation-check="required number" value="${data.vrtc}"/>
</td>
<th class="th">면적(㎡)</th>
<th class="th"><span class="required">*</span> 면적(㎡)</th>
<td>
<input type="text" id="area" name="area" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 45.94" validation-check="number" value="${data.area}"/>
<input type="text" id="area" name="area" class="input decimalMask" style="width: 120px;" maxlength="10" placeholder="예) 45.94" validation-check="required number" value="${data.area}"/>
</td>
</tr>
<tr>
@ -130,9 +130,7 @@
// 닫기 버튼 클릭 이벤트
$('#btnClose, #btnCloseTop').on('click', function() {
if (confirm('작업을 취소하시겠습니까?')) {
window.close();
}
window.close();
});
// 각 드롭다운 초기화 (XIT 공통 컴포넌트 사용)
@ -329,24 +327,34 @@
* 중요한 로직 주석: validation 체크 후 mode에 따라 등록/수정 API를 호출한다.
*/
function saveActInfo() {
// 입력값 검증
if (!validateForm()) {
// 중요로직: validateFormByAttributes를 사용하여 모든 validation-check 속성 검증
if (!validateFormByAttributes('actInfoForm')) {
return;
}
var self = this;
var crdnYr = '${crdnYr}';
var crdnNo = '${crdnNo}';
// 중요로직: 위치정보ID 가져오기 (부모창에서)
var pstnInfoId = '';
if (window.opener && window.opener.CrdnDetailViewPstn && window.opener.CrdnDetailViewPstn.grid && window.opener.CrdnDetailViewPstn.grid.instance) {
var pstnData = window.opener.CrdnDetailViewPstn.grid.instance.getData();
if (pstnData && pstnData.length > 0) {
pstnInfoId = pstnData[0].pstnInfoId || '';
}
}
if (!pstnInfoId) {
alert('위치정보가 등록되어 있지 않습니다. 위치정보를 먼저 등록해주세요.');
return;
}
var mode = $('#mode').val();
var formData = $('#actInfoForm').serialize();
// 세션에서 사용자 정보 가져오기 (등록자/수정자)
<c:choose>
<c:when test="${mode eq 'update'}">
formData += '&mdfr=' + encodeURIComponent('${sessionScope.loginVO.userId}');
</c:when>
<c:otherwise>
formData += '&rgtr=' + encodeURIComponent('${sessionScope.loginVO.userId}');
formData += '&sggCd=' + encodeURIComponent('${sessionScope.loginVO.sggCd}');
</c:otherwise>
</c:choose>
+ '&crdnYr=' + encodeURIComponent(crdnYr);
+ '&crdnNo=' + encodeURIComponent(crdnNo);
+ '&pstnInfoId=' + encodeURIComponent(pstnInfoId);
var url = (mode === 'update') ?
'<c:url value="/crdn/crndRegistAndView/crdnActInfo/update.ajax"/>' :
@ -354,9 +362,9 @@
var actionText = (mode === 'update') ? '수정' : '등록';
if (!confirm('불법행위정보를 ' + actionText + '하시겠습니까?')) {
return;
}
//if (!confirm('불법행위정보를 ' + actionText + '하시겠습니까?')) {
// return;
//}
$.ajax({
url: url,
@ -375,67 +383,8 @@
} else {
alert(response.message || actionText + ' 중 오류가 발생했습니다.');
}
},
error: function(xhr, status, error) {
console.error('불법행위정보 ' + actionText + ' 오류:', error);
alert(actionText + ' 중 시스템 오류가 발생했습니다.');
}
});
}
/**
* 폼 입력값 검증 함수
* 중요한 로직 주석: 필수 입력 필드와 숫자 형식 검증을 수행한다.
*/
function validateForm() {
// 필수 입력 검증
var actBgngYmd = $('#actBgngYmd').val().trim();
var actTypeCd = $('#actTypeCd').val().trim();
if (!actBgngYmd) {
alert('행위시작일자를 입력해 주세요.');
$('#actBgngYmd').focus();
return false;
}
if (!actTypeCd) {
alert('행위유형을 선택해 주세요.');
$('#actTypeCd').focus();
return false;
}
// 숫자 필드 검증
var numberFields = ['hgt', 'wdth', 'vrtc', 'area'];
for (var i = 0; i < numberFields.length; i++) {
var fieldId = numberFields[i];
var value = $('#' + fieldId).val().trim();
if (value && !isValidNumber(value)) {
alert(getFieldLabel(fieldId) + ' 값이 올바르지 않습니다. 숫자만 입력해 주세요.');
$('#' + fieldId).focus();
return false;
}
}
return true;
}
/**
* 숫자 유효성 검증
*/
function isValidNumber(value) {
return /^\d+(\.\d+)?$/.test(value);
}
/**
* 필드 레이블 반환
*/
function getFieldLabel(fieldId) {
var labels = {
'hgt': '높이',
'wdth': '가로',
'vrtc': '세로',
'area': '면적'
};
return labels[fieldId] || fieldId;
}
</script>

@ -68,16 +68,31 @@
width: 120
},
{
header: '위반법규1',
header: '위반법규',
name: 'vltnLwrgCd1Nm',
align: 'center',
width: 120
width: 120,
formatter: function (e) {
var result = e.row.vltnLwrgCd1Nm || '';
if (e.row.vltnLaw1) {
result += ' (' + e.row.vltnLaw1 + ')';
}
return result;
}
},
{
header: '위반법규2',
name: 'vltnLwrgCd2Nm',
align: 'center',
width: 120
width: 120,
hidden: true,
formatter: function (e) {
var result = e.row.vltnLwrgCd2Nm || '';
if (e.row.vltnLaw2) {
result += ' (' + e.row.vltnLaw2 + ')';
}
return result;
}
},
{
header: '구조지수',
@ -154,11 +169,12 @@
serializer: function(params) {
// 기본 파라미터 (페이지 정보 등)
var defaultParams = $.param(params);
// 검색 조건 가져오기
var searchCond = window.CrdnDetailView ? window.CrdnDetailView.getSearchCondition() : {};
var searchParams = $.param(searchCond);
// detailView 화면의 hidden 입력값에서 단속연도/번호를 가져온다.
var crdnYr = $('#crdnYr').val();
var crdnNo = $('#crdnNo').val();
var extra = $.param({ crdnYr: crdnYr, crdnNo: crdnNo });
// 모든 파라미터 조합
return defaultParams + '&' + searchParams;
return defaultParams + '&' + extra;
}
};
},

@ -116,11 +116,12 @@
serializer: function(params) {
// 기본 파라미터 (페이지 정보 등)
var defaultParams = $.param(params);
// 검색 조건 가져오기
var searchCond = window.CrdnDetailView ? window.CrdnDetailView.getSearchCondition() : {};
var searchParams = $.param(searchCond);
// detailView 화면의 hidden 입력값에서 단속연도/번호를 가져온다.
var crdnYr = $('#crdnYr').val();
var crdnNo = $('#crdnNo').val();
var extra = $.param({ crdnYr: crdnYr, crdnNo: crdnNo });
// 모든 파라미터 조합
return defaultParams + '&' + searchParams;
return defaultParams + '&' + extra;
}
};
},

@ -175,23 +175,6 @@
(function(window, $) {
'use strict';
var SEARCH_COND = {};
// 검색정보 설정
var setSearchCond = function() {
var schCrdnYr = $.trim(nvl($("#schCrdnYr").val(), ""));
var schCrdnNo = $.trim(nvl($("#schCrdnNo").val(), ""));
var schRgnSeCd = $.trim(nvl($("#schRgnSeCd").val(), ""));
var schDsclMthdCd = $.trim(nvl($("#schDsclMthdCd").val(), ""));
var schExmnr = $.trim(nvl($("#schExmnr").val(), ""));
SEARCH_COND.schCrdnYr = schCrdnYr;
SEARCH_COND.schCrdnNo = schCrdnNo;
SEARCH_COND.schRgnSeCd = schRgnSeCd;
SEARCH_COND.schDsclMthdCd = schDsclMthdCd;
SEARCH_COND.schExmnr = schExmnr;
};
/**
* 단속 상세보기 관리 네임스페이스
*/
@ -482,15 +465,6 @@
console.log(gridName + ' 그리드 모듈이 등록되었습니다.');
},
/**
* 공통 검색 조건 반환 함수
* 각 그리드 모듈에서 검색 시 사용
*/
getSearchCondition: function() {
setSearchCond();
return SEARCH_COND;
},
/**
* 소유자 버튼 상태 제어 함수
* 위치정보 그리드 데이터 유무에 따라 소유자 선택/제거 버튼을 활성화/비활성화
@ -547,7 +521,7 @@
return;
}
var url = '<c:url value="/crdn/crndRegistAndView/crdnActInfo/crdnActInfoRegistPopup.do"/>' + '?crdnYr=' + encodeURIComponent(crdnYr) + '&crdnNo=' + encodeURIComponent(crdnNo) + '&mode=C';
var w = 1200, h = 600;
var w = 1200, h = 480;
var left = Math.max(0, (screen.width - w) / 2);
var top = Math.max(0, (screen.height - h) / 2);
window.open(url, 'actInfoPopup', 'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes');

Loading…
Cancel
Save