김의진 3 months ago
commit babddda45f

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

@ -17,6 +17,9 @@ create table tb_act_info
VRTC decimal(10, 2) null comment '세로',
AREA decimal(10, 2) null comment '면적',
RMRK varchar(1000) null comment '비고',
ACTN_LAST_YMD varchar(8) null comment '조치 최종 일자',
ACTN_WHOL_AREA decimal(10, 2) null comment '조치 총 면적',
ACTN_PRCS_STTS_CD varchar(1) null comment '조치 처리 상태 코드',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',
MDFCN_DT datetime null comment '수정 일시',

@ -0,0 +1,20 @@
create table tb_actn_info
(
ACTN_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 '단속 번호',
ACT_INFO_ID varchar(10) not null comment '행위 정보 ID',
ACTN_YMD varchar(8) null comment '조치 일자',
ACTN_AREA decimal(10, 2) null comment '조치 면적',
ACTN_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 '삭제자'
)
comment '조치 정보';

@ -8,6 +8,7 @@ create table tb_crdn_photo
CRDN_PHOTO_PATH varchar(100) null comment '단속 사진 경로',
CRDN_PHOTO_NM varchar(500) null comment '단속 사진 명',
CRDN_PHOTO_SE_CD char not null comment '단속 사진 구분 코드',
AACTN_INFO_ID varchar(10) null comment '조치 정보 ID';
ORGNL_PHOTO_NM varchar(500) null comment '원본 사진 명',
REG_DT datetime null comment '등록 일시',
RGTR varchar(11) null comment '등록자',

@ -118,4 +118,9 @@ INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, US
-- 가감산율 구분 (ADSBMTN_RT_SE_CD)
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADSBMTN_RT_SE_CD', '1', '가산율', '가산율', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADSBMTN_RT_SE_CD', '2', '감산율', '감산율', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADSBMTN_RT_SE_CD', '2', '감산율', '감산율', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);
-- 조치 처리 상태 (ACTN_PRCS_STTS_CD)
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ACTN_PRCS_STTS_CD', '1', '미조치', '미조치', 1, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ACTN_PRCS_STTS_CD', '2', '부분조치', '부분조치', 2, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_detail (CD_GROUP_ID, CD_ID, CD_NM, CD_DC, SORT_ORDR, USE_YN, ATTRIBUTE1, ATTRIBUTE2, ATTRIBUTE3, ATTRIBUTE4, ATTRIBUTE5, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ACTN_PRCS_STTS_CD', '3', '조치완료', '조치완료', 3, 'Y', NULL, NULL, NULL, NULL, NULL, NOW(), 'SYSTEM', NULL, NULL);

@ -3,7 +3,7 @@ INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, R
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ORG_CD', '조직', '조직을 나타내는 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('USER_STATUS_CD', '사용자 상태', '사용자의 상태 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
-- 신규 코드그룹 추가 (이슈: 적발방법/지목/지종/행정동/소유자구분/지역구분/단속처리상태/단속사진구분/이행업무구분/이행대상자구분/기초공사구분/가감산율구분)
-- 신규 코드그룹 추가 (이슈: 적발방법/지목/지종/행정동/소유자구분/지역구분/단속처리상태/단속사진구분/이행업무구분/이행대상자구분/기초공사구분/가감산율구분/조치처리상태)
-- 중요: 기존 라인 보존, 맨 하단에 추가 (한글 주석)
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('DSCL_MTHD_CD', '적발 방법', '적발(인지) 방법 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('LDCG_CD', '지목', '토지 대표 지목 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
@ -16,4 +16,5 @@ INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, R
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('IMPLT_TASK_SE_CD', '이행 업무 구분', '이행 업무 구분 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('IMPLT_TRPR_SE_CD', '이행 대상자 구분', '이행 대상자 구분 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('BSCS_CSTRN_SE_CD', '기초공사 구분', '기초 공사 구분 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADSBMTN_RT_SE_CD', '가감산율 구분', '가감산율 구분 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ADSBMTN_RT_SE_CD', '가감산율 구분', '가감산율 구분 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);
INSERT INTO ibmsdb.tb_cd_group (CD_GROUP_ID, CD_GROUP_NM, CD_GROUP_DC, USE_YN, REG_DTTM, RGTR, MDFCN_DTTM, MDFR) VALUES ('ACTN_PRCS_STTS_CD', '조치 처리 상태', '조치 처리 상태 코드', 'Y', NOW(), 'SYSTEM', NULL, NULL);

@ -111,14 +111,14 @@ public class CrdnActInfoController {
// 수정 모드인 경우 기존 데이터 및 사진 목록 조회
if ("U".equals(mode) && actInfoId != null) {
data = service.selectActInfoByPk(actInfoId);
// 중요한 로직 주석: 수정 모드에서 기존 등록된 사진 목록을 조회하여 화면에 표시한다.
// 중요한 로직 주석: 수정 모드에서 기존 등록된 모든 사진 목록(단속+조치)을 조회하여 화면에 표시한다.
photoList = photoService.selectPhotoListByActInfoIdAndPhotoSeCd(
CrdnPhotoVO.builder().actInfoId(actInfoId).crdnPhotoSeCd("1").build());
CrdnPhotoVO.builder().actInfoId(actInfoId).build()); // 사진구분코드 없이 전체 조회
}
ModelAndView mav = new ModelAndView("crdn/crndRegistAndView/crdnActInfo/crdnActInfoRegistPopup" + TilesConstants.POPUP);
mav.addObject("data", data);
mav.addObject("photoList", photoList);
mav.addObject("crdnPhotoList", photoList); // 변수명을 JSP에서 사용하는 crdnPhotoList로 변경
mav.addObject("mode", mode);
mav.addObject("crdnYr", crdnYr);
mav.addObject("crdnNo", crdnNo);
@ -213,13 +213,13 @@ public class CrdnActInfoController {
/**
* ( , AJAX)
* : . service .
* : / . service .
* @param vo
* @param photoFiles ()
* @param crdnPhotoSeCd
* @param crdnPhotoFiles ()
* @param actnPhotoFiles ()
* @return
*/
@Operation(summary = "불법위반행위정보 등록", description = "불법위반행위정보를 등록합니다. 사진 파일도 함께 처리 가능합니다.")
@Operation(summary = "불법위반행위정보 등록", description = "불법위반행위정보를 등록합니다. 단속/조치 사진 파일도 함께 처리 가능합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "등록 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
@ -227,11 +227,12 @@ public class CrdnActInfoController {
@PostMapping("/insert.ajax")
public ResponseEntity<?> insertAjax(
@ModelAttribute CrdnActInfoVO vo,
@RequestParam(value = "photoFiles", required = false) List<MultipartFile> photoFiles,
@RequestParam(value = "crdnPhotoSeCd", required = false) String crdnPhotoSeCd) {
log.debug("불법위반행위정보 등록 요청: {}, 파일 개수: {}", vo,
photoFiles != null ? photoFiles.size() : 0);
@RequestParam(value = "crdnPhotoFiles", required = false) List<MultipartFile> crdnPhotoFiles,
@RequestParam(value = "actnPhotoFiles", required = false) List<MultipartFile> actnPhotoFiles) {
log.debug("불법위반행위정보 등록 요청: {}, 단속 사진 개수: {}, 조치 사진 개수: {}",
vo, crdnPhotoFiles != null ? crdnPhotoFiles.size() : 0,
actnPhotoFiles != null ? actnPhotoFiles.size() : 0);
CrdnRegistAndViewVO crdnVO = new CrdnRegistAndViewVO();
@ -243,8 +244,8 @@ public class CrdnActInfoController {
vo.setRgtr(SessionUtil.getUserId());
vo.setMdfr(SessionUtil.getUserId());
// 중요한 로직 주석: service 단에서 행위정보 등록과 사진 파일 처리를 한 트랜잭션으로 처리
int result = service.insertActInfoWithFiles(vo, photoFiles, crdnPhotoSeCd);
// 중요한 로직 주석: service 단에서 행위정보 등록과 단속/조치 사진 파일 처리를 한 트랜잭션으로 처리
int result = service.insertActInfoWithFiles(vo, crdnPhotoFiles, actnPhotoFiles);
if (result > 0) {
return ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS);
@ -256,13 +257,13 @@ public class CrdnActInfoController {
/**
* ( , AJAX)
* : . service .
* : / . service .
* @param vo
* @param photoFiles ()
* @param crdnPhotoSeCd
* @param crdnPhotoFiles ()
* @param actnPhotoFiles ()
* @return
*/
@Operation(summary = "불법위반행위정보 수정", description = "불법위반행위정보를 수정합니다. 사진 파일도 함께 처리 가능합니다.")
@Operation(summary = "불법위반행위정보 수정", description = "불법위반행위정보를 수정합니다. 단속/조치 사진 파일도 함께 처리 가능합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "수정 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
@ -270,17 +271,18 @@ public class CrdnActInfoController {
@PostMapping("/update.ajax")
public ResponseEntity<?> updateAjax(
@ModelAttribute CrdnActInfoVO vo,
@RequestParam(value = "photoFiles", required = false) List<MultipartFile> photoFiles,
@RequestParam(value = "crdnPhotoSeCd", required = false) String crdnPhotoSeCd) {
log.debug("불법위반행위정보 수정 요청: {}, 파일 개수: {}", vo,
photoFiles != null ? photoFiles.size() : 0);
@RequestParam(value = "crdnPhotoFiles", required = false) List<MultipartFile> crdnPhotoFiles,
@RequestParam(value = "actnPhotoFiles", required = false) List<MultipartFile> actnPhotoFiles) {
log.debug("불법위반행위정보 수정 요청: {}, 단속 사진 개수: {}, 조치 사진 개수: {}",
vo, crdnPhotoFiles != null ? crdnPhotoFiles.size() : 0,
actnPhotoFiles != null ? actnPhotoFiles.size() : 0);
vo.setMdfr(SessionUtil.getUserId());
vo.setSggCd(SessionUtil.getSessionVO().getUser().getOrgCd()); // 업데이트 화면에서 신규파일 등록 시 필요
// 중요한 로직 주석: service 단에서 행위정보 수정과 사진 파일 처리를 한 트랜잭션으로 처리
int result = service.updateActInfoWithFiles(vo, photoFiles, crdnPhotoSeCd);
// 중요한 로직 주석: service 단에서 행위정보 수정과 단속/조치 사진 파일 처리를 한 트랜잭션으로 처리
int result = service.updateActInfoWithFiles(vo, crdnPhotoFiles, actnPhotoFiles);
if (result > 0) {
return ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS);
@ -324,14 +326,15 @@ public class CrdnActInfoController {
public String photoView(
@Parameter(description = "행위정보ID") @RequestParam String actInfoId,
@Parameter(description = "사진순번") @RequestParam String crdnPhotoSn,
@Parameter(description = "단속조치사진구분") @RequestParam String crdnPhotoSeCd,
Model model) {
log.debug("단속 사진 보기 요청 - actInfoId: {}, crdnPhotoSn: {}", actInfoId, crdnPhotoSn);
log.debug("단속 사진 보기 요청 - actInfoId: {}, crdnPhotoSn: {}, crdnPhotoSeCd: {}", actInfoId, crdnPhotoSn, crdnPhotoSeCd);
// 중요한 로직 주석: 해당 행위정보의 모든 사진 목록 조회
CrdnPhotoVO searchVO = CrdnPhotoVO.builder()
.actInfoId(actInfoId)
.crdnPhotoSeCd("1") // 단속 사진
.crdnPhotoSeCd(crdnPhotoSeCd) // 단속 사진
.build();
List<CrdnPhotoVO> photoList = photoService.selectPhotoListByActInfoIdAndPhotoSeCd(searchVO);

@ -27,13 +27,13 @@ public interface CrdnActInfoService {
/**
* ( )
* : . .
* : / . .
* @param vo
* @param photoFiles ()
* @param crdnPhotoSeCd
* @param crdnPhotoFiles ()
* @param actnPhotoFiles ()
* @return
*/
int insertActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> photoFiles, String crdnPhotoSeCd);
int insertActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> crdnPhotoFiles, List<MultipartFile> actnPhotoFiles);
/**
* (PK )
@ -44,13 +44,13 @@ public interface CrdnActInfoService {
/**
* ( )
* : . .
* : / . .
* @param vo
* @param photoFiles ()
* @param crdnPhotoSeCd
* @param crdnPhotoFiles ()
* @param actnPhotoFiles ()
* @return
*/
int updateActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> photoFiles, String crdnPhotoSeCd);
int updateActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> crdnPhotoFiles, List<MultipartFile> actnPhotoFiles);
/**
* ()

@ -54,9 +54,10 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
@Override
@Transactional
public int insertActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> photoFiles, String crdnPhotoSeCd) {
log.debug("불법위반행위정보 등록 (파일 포함): {}, 파일 개수: {}", vo,
photoFiles != null ? photoFiles.size() : 0);
public int insertActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> crdnPhotoFiles, List<MultipartFile> actnPhotoFiles) {
log.debug("불법위반행위정보 등록 (파일 포함): {}, 단속 사진 개수: {}, 조치 사진 개수: {}",
vo, crdnPhotoFiles != null ? crdnPhotoFiles.size() : 0,
actnPhotoFiles != null ? actnPhotoFiles.size() : 0);
try {
// 중요한 로직 주석: 먼저 행위정보를 등록한다 (insertActInfo 메서드에서 actInfoId가 자동 설정됨)
@ -65,10 +66,16 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
if (result > 0) {
log.debug("행위정보 등록 완료: actInfoId={}", vo.getActInfoId());
// 중요한 로직 주석: 파일이 있는 경우에만 사진 처리를 진행한다
if (photoFiles != null && !photoFiles.isEmpty()) {
int photoResult = photoService.insertPhotosWithFiles(photoFiles, vo.getActInfoId(), crdnPhotoSeCd, vo);
log.debug("사진 파일 처리 완료: actInfoId={}, 등록된 사진 수={}", vo.getActInfoId(), photoResult);
// 중요한 로직 주석: 단속 사진이 있는 경우 처리 (CRDN_PHOTO_SE_CD = '1')
if (crdnPhotoFiles != null && !crdnPhotoFiles.isEmpty()) {
int crdnPhotoResult = photoService.insertPhotosWithFiles(crdnPhotoFiles, vo.getActInfoId(), "1", vo);
log.debug("단속 사진 파일 처리 완료: actInfoId={}, 등록된 단속 사진 수={}", vo.getActInfoId(), crdnPhotoResult);
}
// 중요한 로직 주석: 조치 사진이 있는 경우 처리 (CRDN_PHOTO_SE_CD = '2')
if (actnPhotoFiles != null && !actnPhotoFiles.isEmpty()) {
int actnPhotoResult = photoService.insertPhotosWithFiles(actnPhotoFiles, vo.getActInfoId(), "2", vo);
log.debug("조치 사진 파일 처리 완료: actInfoId={}, 등록된 조치 사진 수={}", vo.getActInfoId(), actnPhotoResult);
}
} else {
log.error("행위정보 등록 실패: {}", vo);
@ -91,9 +98,10 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
@Override
@Transactional
public int updateActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> photoFiles, String crdnPhotoSeCd) {
log.debug("불법위반행위정보 수정 (파일 포함): {}, 파일 개수: {}", vo,
photoFiles != null ? photoFiles.size() : 0);
public int updateActInfoWithFiles(CrdnActInfoVO vo, List<MultipartFile> crdnPhotoFiles, List<MultipartFile> actnPhotoFiles) {
log.debug("불법위반행위정보 수정 (파일 포함): {}, 단속 사진 개수: {}, 조치 사진 개수: {}",
vo, crdnPhotoFiles != null ? crdnPhotoFiles.size() : 0,
actnPhotoFiles != null ? actnPhotoFiles.size() : 0);
try {
// 중요한 로직 주석: 먼저 행위정보를 수정한다
@ -102,11 +110,17 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
if (result > 0) {
log.debug("행위정보 수정 완료: actInfoId={}", vo.getActInfoId());
// 중요한 로직 주석: 새로운 파일이 있는 경우에만 사진 추가 처리를 진행한다
// 중요한 로직 주석: 새로운 단속 사진이 있는 경우 추가 처리 (CRDN_PHOTO_SE_CD = '1')
// 기존 사진의 개별 삭제는 별도의 Ajax 호출로 처리되므로 여기서는 신규 추가만 한다
if (photoFiles != null && !photoFiles.isEmpty()) {
int photoResult = photoService.insertPhotosWithFiles(photoFiles, vo.getActInfoId(), crdnPhotoSeCd, vo);
log.debug("사진 파일 추가 완료: actInfoId={}, 추가된 사진 수={}", vo.getActInfoId(), photoResult);
if (crdnPhotoFiles != null && !crdnPhotoFiles.isEmpty()) {
int crdnPhotoResult = photoService.insertPhotosWithFiles(crdnPhotoFiles, vo.getActInfoId(), "1", vo);
log.debug("단속 사진 파일 추가 완료: actInfoId={}, 추가된 단속 사진 수={}", vo.getActInfoId(), crdnPhotoResult);
}
// 중요한 로직 주석: 새로운 조치 사진이 있는 경우 추가 처리 (CRDN_PHOTO_SE_CD = '2')
if (actnPhotoFiles != null && !actnPhotoFiles.isEmpty()) {
int actnPhotoResult = photoService.insertPhotosWithFiles(actnPhotoFiles, vo.getActInfoId(), "2", vo);
log.debug("조치 사진 파일 추가 완료: actInfoId={}, 추가된 조치 사진 수={}", vo.getActInfoId(), actnPhotoResult);
}
} else {
log.error("행위정보 수정 실패: {}", vo);
@ -130,7 +144,10 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
if (vo.getActInfoId() != null) {
try {
photoService.deletePhotosByActInfoIdAndCrdnPhotoSecd(vo.getActInfoId(), "1", vo.getDltr());
log.debug("행위정보 관련 사진 삭제 완료: actInfoId={}", vo.getActInfoId());
log.debug("행위정보 단속 사진 삭제 완료: actInfoId={}, crdnPhotoSeCd={}", vo.getActInfoId(), "1");
photoService.deletePhotosByActInfoIdAndCrdnPhotoSecd(vo.getActInfoId(), "2", vo.getDltr());
log.debug("행위정보 조치 사진 삭제 완료: actInfoId={}, crdnPhotoSeCd={}", vo.getActInfoId(), "2");
} catch (Exception e) {
log.warn("행위정보 관련 사진 삭제 중 오류 발생: actInfoId={}", vo.getActInfoId(), e);
// 사진 삭제 실패해도 행위정보 삭제는 진행

@ -1,5 +1,9 @@
# Local profile
spring:
servlet:
multipart:
max-file-size: 15MB # 개별 파일 최대 크기를 15MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
max-request-size: 150MB # 전체 요청 최대 크기를 150MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
config:
activate:
on-profile: dev

@ -1,5 +1,9 @@
# Local profile
spring:
servlet:
multipart:
max-file-size: 15MB # 개별 파일 최대 크기를 15MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
max-request-size: 150MB # 전체 요청 최대 크기를 150MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
config:
activate:
on-profile: local
@ -94,7 +98,7 @@ file:
path: d:/data/@projectName@/file
max-size: 10 # 단일 파일 최대 크기 (MB)
max-total-size: 100 # 총 파일 최대 크기 (MB)
max-files: 10 # 최대 파일 개수
max-files: 20 # 최대 파일 개수
allowed-extensions: hwp,jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt,zip
real-file-delete: true # 실제 파일 삭제 여부
sub-dirs:

@ -1,5 +1,9 @@
# Local profile
spring:
servlet:
multipart:
max-file-size: 15MB # 개별 파일 최대 크기를 15MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
max-request-size: 150MB # 전체 요청 최대 크기를 150MB로 증가, multipart 를 이용시 header, type 등 정보가 붙어서 over head 가 발생할 수 있다고 함.
config:
activate:
on-profile: prd

@ -7,9 +7,7 @@ server:
charset: UTF-8
enabled: true
force: true # 서버가 지정한 인코딩(UTF-8)을 항상 강제로 적용, 클라이언트의 요청과 상관없이 서버 인코딩이 우선
multipart:
max-file-size: 10MB
max-request-size: 50MB
Globals:
DbType: maria

@ -209,7 +209,7 @@
VRTC = #{vrtc},
AREA = #{area},
RMRK = #{rmrk},
ACTN_YMD = #{actnYmd},
ACTN_YMD = replace(#{actnYmd},'-',''),
ACTN_AREA = #{actnArea},
ACTN_RMRK = #{actnRmrk},
MDFCN_DT = NOW(),

@ -25,7 +25,9 @@
FROM tb_crdn_photo p
LEFT JOIN tb_cd_detail cd ON cd.CD_GROUP_ID = 'CRDN_PHOTO_SE_CD' AND cd.CD_ID = p.CRDN_PHOTO_SE_CD
WHERE p.ACT_INFO_ID = #{actInfoId}
<if test='crdnPhotoSeCd != null and crdnPhotoSeCd != ""'>
AND p.CRDN_PHOTO_SE_CD = #{crdnPhotoSeCd}
</if>
AND p.DEL_YN = 'N'
ORDER BY p.CRDN_PHOTO_SN ASC
</select>

@ -45,7 +45,7 @@
pi.PSTN_IDX_NO, -- 위치지수 NO
pi.PSTN_IDX, -- 위치지수
substring(t3.ACT_BGNG_YMD,1,4) as ELPS_YR_RDVLRT_YR,
CASE WHEN (CAST(substring(t3.ACT_BGNG_YMD,1,4) as SIGNED)-CAST(t1.CRDN_YR as SIGNED)) >= strct.RDVLRT_CN_YR_CNT THEN strct.LAST_YR_RDVLRT
CASE WHEN ( CAST(t1.CRDN_YR as SIGNED) - CAST(substring(t3.ACT_BGNG_YMD,1,4) as SIGNED) ) >= strct.RDVLRT_CN_YR_CNT THEN strct.LAST_YR_RDVLRT
ELSE 1-(
(CAST(t1.CRDN_YR as SIGNED) - CAST(substring(t3.ACT_BGNG_YMD,1,4) as SIGNED))
* strct.DPRT

@ -20,15 +20,15 @@
<a href="#" class="pop-x-btn modalclose" id="btnCloseTop"></a>
</div>
<div class="popup_con">
<div class="forms_table_non">
<!-- 중요로직: 단속연도/번호는 부모창에서 전달받아 그대로 저장에 사용 -->
<form id="actInfoForm" name="actInfoForm">
<input type="hidden" id="mode" name="mode" value="${mode}" />
<input type="hidden" id="actInfoId" name="actInfoId" value="${data.actInfoId}" />
<input type="hidden" id="crdnYr" name="crdnYr" value="${crdnYr}" />
<input type="hidden" id="crdnNo" name="crdnNo" value="${crdnNo}" />
<input type="hidden" id="pstnInfoId" name="pstnInfoId" value="${pstnInfoId}" />
<!-- 중요로직: 단속연도/번호는 부모창에서 전달받아 그대로 저장에 사용 -->
<form id="actInfoForm" name="actInfoForm">
<input type="hidden" id="mode" name="mode" value="${mode}" />
<input type="hidden" id="actInfoId" name="actInfoId" value="${data.actInfoId}" />
<input type="hidden" id="crdnYr" name="crdnYr" value="${crdnYr}" />
<input type="hidden" id="crdnNo" name="crdnNo" value="${crdnNo}" />
<input type="hidden" id="pstnInfoId" name="pstnInfoId" value="${pstnInfoId}" />
<div class="forms_table_non">
<table>
<colgroup>
<col style="width: 18%;" />
@ -74,21 +74,21 @@
<tr>
<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="required 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" value="${data.wdth}"/>
</td>
<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="required 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" value="${data.vrtc}"/>
</td>
</tr>
<tr>
<th class="th"><span class="required">*</span> 면적(㎡)</th>
<td>
<input type="text" id="area" name="area" class="input decimalMask" style="width: 120px;" maxlength="12" placeholder="예) 45.94" validation-check="required number" value="${data.area}"/>
<input type="text" id="area" name="area" class="input decimalMask" style="width: 120px;" maxlength="12" placeholder="예) 45.94" validation-check="required" value="${data.area}"/>
</td>
<th class="th">높이(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" value="${data.hgt}"/>
</td>
</tr>
<tr>
@ -98,33 +98,97 @@
</td>
</tr>
<tr>
<th class="th">행위 사진</th>
<th class="th">단속 사진</th>
<td colspan="3">
<!-- 중요한 로직 주석: 파일 업로드와 미리보기 기능을 제공한다 -->
<div class="file-upload-section">
<!-- 중요한 로직 주석: 단속 사진 업로드와 미리보기 기능 (CRDN_PHOTO_SE_CD = 1) -->
<div class="file-upload-section" data-photo-type="1">
<div class="file-upload-controls">
<label for="photoFiles" class="file-upload-btn smallb-2">
<span>사진 선택</span>
<input type="file" id="photoFiles" name="photoFiles" accept="image/*" multiple style="display: none;">
<label for="crdnPhotoFiles" class="file-upload-btn smallb-2">
<span>단속 사진 선택</span>
<input type="file" id="crdnPhotoFiles" name="crdnPhotoFiles" accept="image/*" multiple style="display: none;">
</label>
<input type="hidden" id="crdnPhotoSeCd" name="crdnPhotoSeCd" value="1"/>
<span class="file-info">최대 10개, 각 10MB 이하의 이미지 파일만 업로드 가능합니다.</span>
<span class="file-info">최대 10개, 각 20MB 이하의 이미지 파일만 업로드 가능합니다.</span>
</div>
<div class="photo-preview-container" id="photoPreviewContainer">
<!-- 중요한 로직 주석: 수정 모드에서 기존 등록된 사진들을 표시한다 -->
<c:if test="${mode eq 'U' && not empty photoList}">
<c:forEach var="photo" items="${photoList}">
<div class="photo-preview-item existing-photo" data-act-info-id="${photo.actInfoId}" data-photo-sn="${photo.crdnPhotoSn}">
<div class="photo-thumbnail">
<img src="<c:url value='/crdn/crndRegistAndView/crdnActInfo/downloadPhoto.do'/>?actInfoId=${photo.actInfoId}&crdnPhotoSn=${photo.crdnPhotoSn}"
alt="${photo.orgnlPhotoNm}" onclick="viewOriginalPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}')">
<div class="photo-preview-container" id="crdnPhotoPreviewContainer">
<!-- 중요한 로직 주석: 수정 모드에서 기존 등록된 단속 사진들을 표시한다 -->
<c:if test="${mode eq 'U' && not empty crdnPhotoList}">
<c:forEach var="photo" items="${crdnPhotoList}">
<c:if test="${photo.crdnPhotoSeCd eq '1'}">
<div class="photo-preview-item existing-photo" data-act-info-id="${photo.actInfoId}" data-photo-sn="${photo.crdnPhotoSn}" data-photo-type="1">
<div class="photo-thumbnail">
<img src="<c:url value='/crdn/crndRegistAndView/crdnActInfo/downloadPhoto.do'/>?actInfoId=${photo.actInfoId}&crdnPhotoSn=${photo.crdnPhotoSn}"
alt="${photo.orgnlPhotoNm}" onclick="viewOriginalPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}', '${photo.crdnPhotoSeCd}')">
</div>
<div class="photo-info">
<div class="photo-name" title="${photo.orgnlPhotoNm}">${photo.orgnlPhotoNm}</div>
<div class="photo-type">[단속]</div>
<button type="button" class="delete-photo-btn" onclick="deleteExistingPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}', '1')">삭제</button>
</div>
</div>
<div class="photo-info">
<div class="photo-name" title="${photo.orgnlPhotoNm}">${photo.orgnlPhotoNm}</div>
<div class="photo-type">[${photo.crdnPhotoSeCdNm}]</div>
<button type="button" class="delete-photo-btn" onclick="deleteExistingPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}')">삭제</button>
</c:if>
</c:forEach>
</c:if>
</div>
</div>
</td>
</tr>
</table>
</div>
<div class="forms_table_non mt-20">
<table>
<colgroup>
<col style="width: 18%;" />
<col style="width: 32%;" />
<col style="width: 18%;" />
<col style="width: 32%;" />
</colgroup>
<tr>
<th class="th">조치일자</th>
<td>
<input type="text" id="actnYmd" name="actnYmd" class="input calender datepicker" maxlength="10" style="width: 120px;" autocomplete="off" value="${dateUtil:formatDateString(data.actnYmd)}"/>
</td>
<th class="th">조치 면적(㎡)</th>
<td>
<input type="text" id="actnArea" name="actnArea" class="input decimalMask" style="width: 120px;" maxlength="12" placeholder="예) 45.94" value="${data.actnArea}"/>
</td>
</tr>
<tr>
<th class="th">비고</th>
<td colspan="3">
<textarea id="actnRmrk" name="actnRmrk" class="textarea" rows="3" maxlength="1000" style="height: 80px;">${data.rmrk}</textarea>
</td>
</tr>
<tr>
<th class="th">조치 사진</th>
<td colspan="3">
<!-- 중요한 로직 주석: 조치 사진 업로드와 미리보기 기능 (CRDN_PHOTO_SE_CD = 2) -->
<div class="file-upload-section" data-photo-type="2">
<div class="file-upload-controls">
<label for="actnPhotoFiles" class="file-upload-btn smallb-2">
<span>조치 사진 선택</span>
<input type="file" id="actnPhotoFiles" name="actnPhotoFiles" accept="image/*" multiple style="display: none;">
</label>
<span class="file-info">최대 10개, 각 20MB 이하의 이미지 파일만 업로드 가능합니다.</span>
</div>
<div class="photo-preview-container" id="actnPhotoPreviewContainer">
<!-- 중요한 로직 주석: 수정 모드에서 기존 등록된 조치 사진들을 표시한다 -->
<c:if test="${mode eq 'U' && not empty crdnPhotoList}">
<c:forEach var="photo" items="${crdnPhotoList}">
<c:if test="${photo.crdnPhotoSeCd eq '2'}">
<div class="photo-preview-item existing-photo" data-act-info-id="${photo.actInfoId}" data-photo-sn="${photo.crdnPhotoSn}" data-photo-type="2">
<div class="photo-thumbnail">
<img src="<c:url value='/crdn/crndRegistAndView/crdnActInfo/downloadPhoto.do'/>?actInfoId=${photo.actInfoId}&crdnPhotoSn=${photo.crdnPhotoSn}"
alt="${photo.orgnlPhotoNm}" onclick="viewOriginalPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}', '${photo.crdnPhotoSeCd}')">
</div>
<div class="photo-info">
<div class="photo-name" title="${photo.orgnlPhotoNm}">${photo.orgnlPhotoNm}</div>
<div class="photo-type">[조치]</div>
<button type="button" class="delete-photo-btn" onclick="deleteExistingPhoto('${photo.actInfoId}', '${photo.crdnPhotoSn}', '2')">삭제</button>
</div>
</div>
</div>
</c:if>
</c:forEach>
</c:if>
</div>
@ -132,8 +196,9 @@
</td>
</tr>
</table>
</form>
</div>
</div>
</form>
</div>
<div class="popup_foot">
<button type="button" id="btnSave" class="newbtns bg1">저장</button>
@ -489,10 +554,17 @@
}
});
// 중요한 로직 주석: 선택된 파일들을 FormData에 추가 (있는 경우에만)
if (selectedFiles && selectedFiles.length > 0) {
for (var i = 0; i < selectedFiles.length; i++) {
formData.append('photoFiles', selectedFiles[i]);
// 중요한 로직 주석: 선택된 단속 사진들을 FormData에 추가 (있는 경우에만)
if (crdnSelectedFiles && crdnSelectedFiles.length > 0) {
for (var i = 0; i < crdnSelectedFiles.length; i++) {
formData.append('crdnPhotoFiles', crdnSelectedFiles[i]);
}
}
// 중요한 로직 주석: 선택된 조치 사진들을 FormData에 추가 (있는 경우에만)
if (actnSelectedFiles && actnSelectedFiles.length > 0) {
for (var i = 0; i < actnSelectedFiles.length; i++) {
formData.append('actnPhotoFiles', actnSelectedFiles[i]);
}
}
@ -531,79 +603,116 @@
}
}
// 전역 변수: 선택된 파일들을 보관
var selectedFiles = [];
// 전역 변수: 선택된 파일들을 사진 종류별로 보관
var crdnSelectedFiles = []; // 단속 사진 파일들
var actnSelectedFiles = []; // 조치 사진 파일들
/**
* 파일 업로드 초기화
* 중요한 로직 주석: 파일 선택 이벤트와 드래그앤드롭 이벤트를 추가하고 파일 유효성 검증을 초기화한다.
* 중요한 로직 주석: 단속/조치 사진 각각의 파일 선택 이벤트와 드래그앤드롭 이벤트를 추가한다.
*/
function initFileUpload() {
var dropZone = $('#photoPreviewContainer');
// 단속 사진 영역
var crdnDropZone = $('#crdnPhotoPreviewContainer');
var actnDropZone = $('#actnPhotoPreviewContainer');
// 단속 사진 파일 선택 이벤트
$('#crdnPhotoFiles').on('change', function(e) {
handleFileSelect(e.target.files, '1');
});
// 파일 선택 이벤트
$('#photoFiles').on('change', function(e) {
handleFileSelect(e.target.files);
// 조치 사진 파일 선택 이벤트
$('#actnPhotoFiles').on('change', function(e) {
handleFileSelect(e.target.files, '2');
});
// 중요로직: 드래그앤드롭으로 파일 업로드 처리
dropZone.on('dragover', function(e) {
// 중요로직: 단속 사진 드래그앤드롭 처리
crdnDropZone.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
dropZone.addClass('drag-over');
crdnDropZone.addClass('drag-over');
});
dropZone.on('dragleave', function(e) {
crdnDropZone.on('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
dropZone.removeClass('drag-over');
crdnDropZone.removeClass('drag-over');
});
dropZone.on('drop', function(e) {
crdnDropZone.on('drop', function(e) {
e.preventDefault();
e.stopPropagation();
dropZone.removeClass('drag-over');
crdnDropZone.removeClass('drag-over');
var files = e.originalEvent.dataTransfer.files;
if (files.length > 0) {
// 기존 파일 선택 로직 재사용
handleFileSelect(files);
handleFileSelect(files, '1'); // 단속 사진으로 처리
}
});
// 중요로직: 조치 사진 드래그앤드롭 처리
actnDropZone.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
actnDropZone.addClass('drag-over');
});
actnDropZone.on('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
actnDropZone.removeClass('drag-over');
});
actnDropZone.on('drop', function(e) {
e.preventDefault();
e.stopPropagation();
actnDropZone.removeClass('drag-over');
var files = e.originalEvent.dataTransfer.files;
if (files.length > 0) {
handleFileSelect(files, '2'); // 조치 사진으로 처리
}
});
}
/**
* 파일 선택 처리
* 중요한 로직 주석: 선택된 파일들을 검증하고 파일명만 표시한다. 미리보기는 하지 않는다.
* 중요한 로직 주석: 사진 종류별로 선택된 파일들을 검증하고 각자의 영역에 미리보기를 표시한다.
* @param {FileList} files - 선택된 파일 목록
* @param {string} photoType - 사진 종류 ('1': 단속, '2': 조치)
*/
function handleFileSelect(files) {
function handleFileSelect(files, photoType) {
if (!files || files.length === 0) {
return;
}
// 파일 개수 제한 체크
var existingPhotos = $('.photo-preview-item.existing-photo').length;
var newFiles = selectedFiles.length + files.length;
var containerSelector = photoType === '1' ? '#crdnPhotoPreviewContainer' : '#actnPhotoPreviewContainer';
var selectedFilesArray = photoType === '1' ? crdnSelectedFiles : actnSelectedFiles;
var photoTypeName = photoType === '1' ? '단속' : '조치';
// 파일 개수 제한 체크 (각 종류별로 10개까지)
var existingPhotos = $(containerSelector + ' .photo-preview-item.existing-photo').length;
var newFiles = selectedFilesArray.length + files.length;
if (existingPhotos + newFiles > 10) {
alert('최대 10개의 사진만 업로드할 수 있습니다.');
alert('최대 10개의 ' + photoTypeName + ' 사진만 업로드할 수 있습니다.');
return;
}
// 각 파일에 대해 검증 및 파일명만 표시
// 각 파일에 대해 검증 및 미리보기 생성
for (var i = 0; i < files.length; i++) {
var file = files[i];
// 파일 유효성 검증
if (!validateFile(file)) {
continue;
}
// 선택된 파일 목록에 추가
selectedFiles.push(file);
// 미리보기 생성 (클릭 이벤트 없음)
createPhotoPreview(file);
// 해당 종류의 선택된 파일 목록에 추가
selectedFilesArray.push(file);
// 미리보기 생성
createPhotoPreview(file, photoType);
}
}
@ -615,7 +724,7 @@
// 파일 크기 체크 (10MB)
var maxSize = 10 * 1024 * 1024;
if (file.size > maxSize) {
alert(file.name + ' 파일이 10MB를 초과합니다.');
alert(file.name + ' 파일이 20MB를 초과합니다.');
return false;
}
@ -639,40 +748,46 @@
}
/**
* 사진 미리보기 생성 (클릭 이벤트 없음)
* 중요한 로직 주석: 선택된 파일을 읽어서 미리보기 이미지를 생성한다. 클릭 시 원본 이미지 팝업은 제공하지 않는다.
* 사진 미리보기 생성
* 중요한 로직 주석: 선택된 파일을 읽어서 해당 종류의 미리보기 영역에 이미지를 생성한다.
* @param {File} file - 미리보기를 생성할 파일
* @param {string} photoType - 사진 종류 ('1': 단속, '2': 조치)
*/
function createPhotoPreview(file) {
function createPhotoPreview(file, photoType) {
var reader = new FileReader();
var containerSelector = photoType === '1' ? '#crdnPhotoPreviewContainer' : '#actnPhotoPreviewContainer';
var photoTypeName = photoType === '1' ? '단속' : '조치';
reader.onload = function(e) {
var photoSeCd = $('#crdnPhotoSeCd').val();
var photoSeCdNm = photoSeCd === '1' ? '단속' : '조치';
var previewHtml =
'<div class="photo-preview-item new-photo" data-file-name="' + file.name + '">' +
var previewHtml =
'<div class="photo-preview-item new-photo" data-file-name="' + file.name + '" data-photo-type="' + photoType + '">' +
' <div class="photo-thumbnail">' +
' <img src="' + e.target.result + '" alt="' + file.name + '">' +
' </div>' +
' <div class="photo-info">' +
' <div class="photo-name" title="' + file.name + '">' + file.name + '</div>' +
' <div class="photo-type">[' + photoSeCdNm + ']</div>' +
' <button type="button" class="delete-photo-btn" onclick="deleteNewPhoto(this)">삭제</button>' +
' <div class="photo-type">[' + photoTypeName + ']</div>' +
' <button type="button" class="delete-photo-btn" onclick="deleteNewPhoto(this, \'' + photoType + '\')">삭제</button>' +
' </div>' +
'</div>';
$('#photoPreviewContainer').append(previewHtml);
$(containerSelector).append(previewHtml);
};
reader.readAsDataURL(file);
}
/**
* 기존 사진 삭제
* 중요한 로직 주석: DB에 등록된 사진을 논리삭제하고 화면에서 제거한다.
* @param {string} actInfoId - 행위정보 ID
* @param {string} crdnPhotoSn - 단속사진 순번
* @param {string} photoType - 사진 종류 ('1': 단속, '2': 조치)
*/
function deleteExistingPhoto(actInfoId, crdnPhotoSn) {
if (!confirm('선택한 사진을 삭제하시겠습니까?')) {
function deleteExistingPhoto(actInfoId, crdnPhotoSn, photoType) {
var photoTypeName = photoType === '1' ? '단속' : '조치';
if (!confirm('선택한 ' + photoTypeName + ' 사진을 삭제하시겠습니까?')) {
return;
}
@ -686,7 +801,7 @@
success: function(response) {
if (response && response.success) {
// 화면에서 해당 사진 제거
$('[data-act-info-id="' + actInfoId + '"][data-photo-sn="' + crdnPhotoSn + '"]').remove();
$('[data-act-info-id="' + actInfoId + '"][data-photo-sn="' + crdnPhotoSn + '"][data-photo-type="' + photoType + '"]').remove();
} else {
alert(response.message || '사진 삭제에 실패했습니다.');
}
@ -696,20 +811,23 @@
/**
* 새로 선택한 파일 삭제
* 중요한 로직 주석: 아직 저장되지 않은 파일을 목록에서 제거하고 selectedFiles 배열에서도 제거한다.
* 중요한 로직 주석: 아직 저장되지 않은 파일을 해당 종류의 목록에서 제거하고 selectedFiles 배열에서도 제거한다.
* @param {Element} button - 삭제 버튼 요소
* @param {string} photoType - 사진 종류 ('1': 단속, '2': 조치)
*/
function deleteNewPhoto(button) {
function deleteNewPhoto(button, photoType) {
var $item = $(button).closest('.photo-preview-item');
var fileName = $item.data('file-name');
// selectedFiles 배열에서 해당 파일 제거
for (var i = 0; i < selectedFiles.length; i++) {
if (selectedFiles[i].name === fileName) {
selectedFiles.splice(i, 1);
var selectedFilesArray = photoType === '1' ? crdnSelectedFiles : actnSelectedFiles;
// 해당 종류의 selectedFiles 배열에서 파일 제거
for (var i = 0; i < selectedFilesArray.length; i++) {
if (selectedFilesArray[i].name === fileName) {
selectedFilesArray.splice(i, 1);
break;
}
}
// 화면에서 제거
$item.remove();
}
@ -718,8 +836,8 @@
* 원본 사진 보기 (기존 DB 사진)
* 중요한 로직 주석: 등록된 사진을 photoView 페이지에서 원본 크기로 표시하고 다운로드 버튼을 제공한다.
*/
function viewOriginalPhoto(actInfoId, crdnPhotoSn) {
var url = '<c:url value="/crdn/crndRegistAndView/crdnActInfo/photoView.do"/>?actInfoId=' + actInfoId + '&crdnPhotoSn=' + crdnPhotoSn;
function viewOriginalPhoto(actInfoId, crdnPhotoSn, crdnPhotoSeCd) {
var url = '<c:url value="/crdn/crndRegistAndView/crdnActInfo/photoView.do"/>?actInfoId=' + actInfoId + '&crdnPhotoSn=' + crdnPhotoSn + '&crdnPhotoSeCd=' + crdnPhotoSeCd ;
openPopup(url, 1000, 700, '_blank');
}

@ -16,7 +16,7 @@
■ 4단계 계산 프로세스 (서버에서 순차 처리)
1. 건축물과세시가(bdstTxtnMprc) 계산 - 이부분은 율 이라고 표현되지만 0.85, 1 등 이미지 % 100 처리 된 값으로 표현됨
1. 건축물과세시가(bdstTxtnMprc) 계산 - [경과년수별잔가율, 기초공사율 : 율 이라고 표현되지만 0.85, 1 등, %가 아닌 나누기 100 처리 된 값으로 표현됨]
- 공식: bldgNewPrcCrtrAmt × strctIdx × usgIdx × pstnIdx × (elpsYrRdvlrt) × (bscsCstrnRt)
- 설명: 건물기준시가액 × 구조지수 × 용도지수 × 위치지수 × 경과년수별잔가율 × 기초공사율
- 반올림: setScale(0, RoundingMode.HALF_UP) - 소수점 반올림
@ -79,6 +79,130 @@
│ - 모든 행위정보 완료 시 자동 합산 │
└─────────────────────────────────────────────────────────────────────────────┘
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■ 데이터베이스 테이블 및 컬럼 상세 정보 (쿼리 조회 기준)
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
■ 1. 건물기준시가액 (bldgNewPrcCrtrAmt)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_bldg_new_prc_crtr_amt (건물신축가격기준액)
• 매칭 조건:
- YR = t1.CRDN_YR (단속연도)
- NO = SUBSTRING(용도지수코드,1,1) (용도지수 첫자리 번호)
• 주요 컬럼:
- BLDG_NEW_PRC_CRTR_AMT: 건물신축가격기준액 (DECIMAL(13))
- BDST_USG: 건축물용도 (VARCHAR(60))
• 저장위치: tb_levy_info.BLDG_CRTR_MPRC_AMT / BLDG_NEW_PRC_CRTR_AMT_NO
■ 2. 구조지수 (strctIdx)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_strct_idx (구조지수)
• 주요 컬럼:
- STRCT_IDX_CD: 구조지수코드 (VARCHAR(3)) [PK]
- STRCT_IDX: 구조지수값 (DECIMAL(10,2))
- STRCT_NM: 구조명 (VARCHAR(100))
- RDVLRT_CN_YR_CNT: 잔가율내용연도수 (DECIMAL(2))
- LAST_YR_RDVLRT: 최종연도잔가율 (DECIMAL(4,2))
- DPRT: 감가상각률 (DECIMAL(4,4))
• 추가 계산용 컬럼:
- BSCS_CSTRN_Y_BDST_CMPTTN_RT: 기초공사Y건축물산정비율
- BSCS_CSTRN_N_BDST_CMPTTN_RT: 기초공사N건축물산정비율
- DUP_ETBLDG_BDST_CMPTTN_RT: 복측증축건축물산정비율
• 저장위치: tb_levy_info.STRCT_IDX_CD / STRCT_IDX
■ 3. 용도지수 (usgIdx)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_usg_idx (용도지수)
• 주요 컬럼:
- USG_IDX_CD: 용도지수코드 (VARCHAR(5)) [PK]
- USG_IDX: 용도지수값 (DECIMAL(10,2))
- USG_NM: 용도명 (VARCHAR(300))
- MAIN_USG_NM: 주요용도명 (VARCHAR(100))
- LWR_USG_NM: 하위용도명 (VARCHAR(100))
• 저장위치: tb_levy_info.USG_IDX_CD / USG_IDX
■ 4. 위치지수 (pstnIdx)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_pstn_idx (위치지수)
• 매칭 조건: 공시지가(OALP) BETWEEN 시작가격 AND 종료가격 (범위 매칭)
• 주요 컬럼:
- PSTN_IDX_NO: 위치지수번호 (VARCHAR(2)) [PK]
- PSTN_IDX: 위치지수값 (DECIMAL(10,2))
- BLDG_ANX_LAND_BGNG_PRC: 건물부속토지시작가격 (DECIMAL)
- BLDG_ANX_LAND_END_PRC: 건물부속토지종료가격 (DECIMAL)
• 저장위치: tb_levy_info.PSTN_IDX_NO / PSTN_IDX
■ 5. 경과년수별잔가율 (elpsYrRdvlrt)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 계산 방식: 동적 계산 (구조지수의 감가상각 정보 활용)
• 관련 테이블:
- tb_strct_idx: 감가상각률(DPRT), 잔가율내용연도수(RDVLRT_CN_YR_CNT), 최종연도잔가율(LAST_YR_RDVLRT)
- tb_act_info: 행위시작일자(ACT_BGNG_YMD)
- tb_crdn: 단속연도(CRDN_YR)
• 계산 공식:
- 경과년수 = CAST(단속연도 as SIGNED) - CAST(SUBSTRING(행위시작일자,1,4) as SIGNED)
- IF 경과년수 >= 잔가율내용연도수 THEN 최종연도잔가율
- ELSE 1 - (경과년수 × 감가상각률)
• 저장위치: tb_levy_info.ELPS_YR_RDVLRT
■ 6. 기초공사율 (bscsCstrnRt)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 계산 방식: 구조지수 테이블의 기초공사 관련 비율 조회
• 관련 테이블: tb_strct_idx
• 계산용 컬럼:
- BSCS_CSTRN_Y_BDST_CMPTTN_RT: 기초공사 있음(Y) 건축물산정비율
- BSCS_CSTRN_N_BDST_CMPTTN_RT: 기초공사 없음(N) 건축물산정비율
- DUP_ETBLDG_BDST_CMPTTN_RT: 복측증축 건축물산정비율
• 선택 방식: 기초공사구분코드(bscsCstrnSeCd)에 따라 해당 비율 적용
• 저장위치: tb_levy_info.BSCS_CSTRN_SE_CD / BSCS_CSTRN_RT
■ 7. 가감산시행령률 (adsbmtnEnfcRt)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 기본값: 100 (고정값)
• 관련 테이블: tb_adsbmtn_rt (가감산율)
• 주요 컬럼:
- ADSBMTN_RT_CD: 가감산율코드
- ADSBMTN_RT_SE_CD: 가감산율구분코드 ('1':가산, '2':감산)
- ADSBMTN_RT: 가감산율
• 계산 방식: (100 + 가산율 - 감산율) 형태로 적용
• 저장위치: tb_levy_info.ADSBMTN_ENFC_RT
■ 8. 산정률 (cmpttnRt)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_act_type (행위유형)
• 매칭 조건: ACT_TYPE_CD = 행위정보.행위유형코드
• 주요 컬럼:
- ACT_TYPE_CD: 행위유형코드 (VARCHAR(3)) [PK]
- CMPTTN_RT: 산정률 (DECIMAL(3))
- VLTN_BDST: 위반건축물 (VARCHAR(100))
• 저장위치: tb_levy_info.CMPTTN_RT_CD / CMPTTN_RT
■ 9. 산정률2 (cmpttnRt2)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• 조회 테이블: tb_cmpttn_rt_2 (산정률2)
• 조회 조건: USE_YN = 'Y' (사용중인 항목만)
• 주요 컬럼:
- CMPTTN_RT_2_CD: 산정률2코드
- CMPTTN_RT_2: 산정률2값 (DECIMAL(3))
- VLTN_MTTR: 위반사항
- USE_YN: 사용여부
• 저장위치: tb_levy_info.CMPTTN_RT_2_CD / CMPTTN_RT_2
■ 10. 추가 관련 테이블
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• tb_levy_info: 모든 계산 결과의 최종 저장소 (스냅샷 방식)
• tb_act_info: 행위정보 (행위시작일자, 행위유형코드, 위반면적 등)
• tb_pstn_info: 위치정보 (공시지가 OALP)
• tb_crdn: 단속정보 (단속연도, 단속번호)
■ 계산 흐름 요약
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 기준 데이터 조회: 단속연도, 용도지수, 공시지가 기준으로 각종 마스터 테이블 JOIN
2. 동적 계산: 경과년수별잔가율만 단속연도와 행위시작연도 차이로 실시간 계산
3. 스냅샷 저장: 계산된 모든 값을 tb_levy_info에 저장하여 데이터 변경에 무관하게 복원 가능
4. 이행강제금: 모든 행위정보별 부과총액 합계로 최종 산출
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
-->
@ -187,7 +311,7 @@
<th class="op-cell b-right-0 b-bottom-0"></th>
<th class="b-right-0 b-bottom-0">경과년수<br/>별잔가율</th>
<th class="op-cell b-right-0 b-bottom-0"></th>
<th class="b-right-0 b-bottom-0">기초공율</th>
<th class="b-right-0 b-bottom-0">기초공율</th>
<th class="op-cell b-right-0 b-bottom-0"></th>
<th class="b-right-0 b-bottom-0">건축물<br/>과세시가</th>
<th class="b-bottom-0">시가표준액<br/>(1,000원미만절삭)</th>
@ -213,7 +337,7 @@
<c:forEach var="code" items="${bscsCstrnSeCdList}">
<option value="${code.cdId}">${code.cdNm}</option>
</c:forEach>
</select><!-- 기초공율 -->
</select><!-- 기초공율 -->
</td>
<td rowspan="2" class="op-cell b-right-0">=</td>
<td rowspan="2" class="b-right-0 b-bottom-0"><input type="text" class="input numericMask" id="taxableMarketPrice" readonly/><!-- 건축물과세시가 --></td>
@ -718,23 +842,8 @@
LevyPrvntcPopup.levyInfoIdSelect = existingData.levyInfoId;
// 위반면적 설정
$('#vltnArea').val(existingData.vltnArea || '').trigger('focus');
// 산정률1 설정 (행위유형)
$('#cmpttnRtDisplay').val((existingData.cmpttnRt || '') + ' %'); // 산정률 표시 필드 (퍼센트 포함)
$('#cmpttnRtCd').val(existingData.cmpttnRtCd || ''); // 산정률 코드 설정
$('#cmpttnRt').val(existingData.cmpttnRt || ''); // 산정률 값 설정
$('#cmpttnRtRate').val(existingData.cmpttnRtRate || ''); // 계산용 비율값
// 기초공사구분 및 비율
$('#bscsCstrnSeCd').val(existingData.bscsCstrnSeCd || ''); // 기초공사구분 코드 설정
$('#bscsCstrnRt').val(existingData.bscsCstrnRt || ''); // 기초공사율 값 설정
// 건물기준시가액
$('#bldgNewPrcCrtrAmtNo').val(existingData.bldgNewPrcCrtrAmtNo || ''); // 건물기준시가액 번호 설정
$('#bldgNewPrcCrtrAmtDisplay').val(existingData.bldgNewPrcCrtrAmtDisplay || ''); // 건축물용도명
$('#bldgNewPrcCrtrAmt').val(existingData.bldgCrtrMprcAmt || '').trigger('focus'); // 건물기준시가액 값 설정
// 건축물과세시가
$('#taxableMarketPrice').val(existingData.bdstTxtnMprc || '').trigger('focus');
// 구조지수
$('#strctIdxCd').val(existingData.strctIdxCd || ''); // 구조지수 코드 설정
@ -755,11 +864,31 @@
$('#elpsYrRdvlrt_yr').val((existingData.elpsYrRdvlrtYr || '') + ' 년'); // 행위시작년도
$('#elpsYrRdvlrt').val(existingData.elpsYrRdvlrt || ''); // 경과년수별잔가율 값 설정
// 산정률2
$('#cmpttnRt2Cd').val(existingData.cmpttnRt2Cd || ''); // 산정률2 코드 설정
$('#cmpttnRt2').val(existingData.cmpttnRt2 || ''); // 산정률2 값 설정
$('#cmpttnRt2Display').val((existingData.cmpttnRt2 || '') + ' %'); // 산정률2 표시 필드 (퍼센트 포함)
$('#cmpttnRt2Rate').val(existingData.cmpttnRt2Rate || ''); // 계산용 비율값
// 기초공사구분 및 비율
$('#bscsCstrnSeCd').val(existingData.bscsCstrnSeCd || ''); // 기초공사구분 코드 설정
$('#bscsCstrnRt').val(existingData.bscsCstrnRt || ''); // 기초공사율 값 설정
// 기초공사율 계산을 위한 기준 비율들 저장 (hidden으로)
if (!$('#bscsCstrnYBdstCmpttnRt').length) {
$('<input type="hidden" id="bscsCstrnYBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="bscsCstrnNBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="dupEtbldgBdstCmpttnRt">').appendTo('body');
}
$('#bscsCstrnYBdstCmpttnRt').val(existingData.bscsCstrnYBdstCmpttnRt || '');
$('#bscsCstrnNBdstCmpttnRt').val(existingData.bscsCstrnNBdstCmpttnRt || '');
$('#dupEtbldgBdstCmpttnRt').val(existingData.dupEtbldgBdstCmpttnRt || '');
// 건물기준시가액
$('#bldgNewPrcCrtrAmtNo').val(existingData.bldgNewPrcCrtrAmtNo || ''); // 건물기준시가액 번호 설정
$('#bldgNewPrcCrtrAmtDisplay').val(existingData.bldgNewPrcCrtrAmtDisplay || ''); // 건축물용도명
$('#bldgNewPrcCrtrAmt').val(existingData.bldgCrtrMprcAmt || '').trigger('focus'); // 건물기준시가액 값 설정
// 시가표준액
$('#standardMarketPrice').val(existingData.mprcStdAmt || '').trigger('focus');
$('#standardMarketPrice_bottom').val(existingData.mprcStdAmt || '').trigger('focus');
// 위반면적 설정
$('#vltnArea').val(existingData.vltnArea || '').trigger('focus');
// 가감산 정보
$('#adtnRtCd').val(existingData.adtnRtCd || ''); // 가산율 코드 설정
@ -773,28 +902,6 @@
$('#adsbmtnEnfcRt').val(existingData.adsbmtnEnfcRt || '100'); // 가감산시행령률 값 설정
$('#adsbmtnEnfcRtDisplay').val((existingData.adsbmtnEnfcRt || '100') + ' %'); // 가감산시행령률 표시 필드 (퍼센트 포함)
// 산정액 및 부과총액
$('#cmpttnAmt').val(existingData.cmpttnAmt || '').trigger('focus'); // 산정액 값 설정
$('#levyWholAmt').val(existingData.levyWholAmt || ''); // 부과총액 값 설정
$('#levyWholAmtDisplay').text((existingData.levyWholAmt ? Number(existingData.levyWholAmt).toLocaleString() : '0') + ' 원');
// 시가표준액
$('#standardMarketPrice').val(existingData.mprcStdAmt || '').trigger('focus');
$('#standardMarketPrice_bottom').val(existingData.mprcStdAmt || '').trigger('focus');
// 건축물과세시가
$('#taxableMarketPrice').val(existingData.bdstTxtnMprc || '').trigger('focus');
// 기초공사율 계산을 위한 기준 비율들 저장 (hidden으로)
if (!$('#bscsCstrnYBdstCmpttnRt').length) {
$('<input type="hidden" id="bscsCstrnYBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="bscsCstrnNBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="dupEtbldgBdstCmpttnRt">').appendTo('body');
}
$('#bscsCstrnYBdstCmpttnRt').val(existingData.bscsCstrnYBdstCmpttnRt || '');
$('#bscsCstrnNBdstCmpttnRt').val(existingData.bscsCstrnNBdstCmpttnRt || '');
$('#dupEtbldgBdstCmpttnRt').val(existingData.dupEtbldgBdstCmpttnRt || '');
// 가감산 버튼 텍스트 업데이트
if (existingData.adtnRt && parseFloat(existingData.adtnRt) > 0) {
$('#btnOpenAddPopup').text('+ 가산 [' + existingData.adtnRt + '%]');
@ -808,6 +915,23 @@
$('#btnOpenMinusPopup').text('- 감산 [0%]');
}
// 산정률1 설정 (행위유형)
$('#cmpttnRtDisplay').val((existingData.cmpttnRt || '') + ' %'); // 산정률 표시 필드 (퍼센트 포함)
$('#cmpttnRtCd').val(existingData.cmpttnRtCd || ''); // 산정률 코드 설정
$('#cmpttnRt').val(existingData.cmpttnRt || ''); // 산정률 값 설정
$('#cmpttnRtRate').val(existingData.cmpttnRtRate || ''); // 계산용 비율값
// 산정률2
$('#cmpttnRt2Cd').val(existingData.cmpttnRt2Cd || ''); // 산정률2 코드 설정
$('#cmpttnRt2').val(existingData.cmpttnRt2 || ''); // 산정률2 값 설정
$('#cmpttnRt2Display').val((existingData.cmpttnRt2 || '') + ' %'); // 산정률2 표시 필드 (퍼센트 포함)
$('#cmpttnRt2Rate').val(existingData.cmpttnRt2Rate || ''); // 계산용 비율값
// 산정액 및 부과총액
$('#cmpttnAmt').val(existingData.cmpttnAmt || '').trigger('focus'); // 산정액 값 설정
$('#levyWholAmt').val(existingData.levyWholAmt || ''); // 부과총액 값 설정
$('#levyWholAmtDisplay').text((existingData.levyWholAmt ? Number(existingData.levyWholAmt).toLocaleString() : '0') + ' 원');
// 기존 데이터 로딩 시 계산하기 버튼 숨김 (이미 계산된 결과가 있으므로)
LevyPrvntcPopup.hideCalculateButton(); // 기존 부과정보 로드 시 계산하기 버튼 숨김
@ -822,26 +946,6 @@
*/
var setActInfoToForm = function(rowData) {
var vltnArea = rowData.calcArea; // 면적 - 조치면적 : 위반면적
$('#vltnArea').val(vltnArea).trigger('focus').trigger('change'); // 위반면적, change 이벤트 트리거
// 행위유형에 따른 산정률1 설정
$('#cmpttnRtDisplay').val(rowData.actCmpttnRt+ ' %'); // 산정률 표시 필드 (퍼센트 포함)
$('#cmpttnRtCd').val(rowData.actTypeCd); // 산정률 코드 설정
$('#cmpttnRt').val(rowData.actCmpttnRt); // 산정률 값 설정
$('#cmpttnRtRate').val(rowData.actCmpttnRtRate); // 산정률 계산용 비율값 설정
$('#bscsCstrnRt').val(''); // 기초공사율
$('#bscsCstrnSeCd').val(''); // 기초공사구분
// 기초공사율 계산을 위한 기준 비율들 초기화 (hidden으로)
if (!$('#bscsCstrnYBdstCmpttnRt').length) {
$('<input type="hidden" id="bscsCstrnYBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="bscsCstrnNBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="dupEtbldgBdstCmpttnRt">').appendTo('body');
}
$('#bscsCstrnYBdstCmpttnRt').val('');
$('#bscsCstrnNBdstCmpttnRt').val('');
$('#dupEtbldgBdstCmpttnRt').val('');
$('#bldgNewPrcCrtrAmtNo').val(rowData.bldgNewPrcCrtrAmtNo); // 건물기준시가액(NO)
$('#bldgNewPrcCrtrAmtDisplay').val(rowData.bdstUsg); // 건물기준시가액(건축물 용도)
@ -862,8 +966,30 @@
$('#elpsYrRdvlrt_yr').val(rowData.elpsYrRdvlrtYr+' 년'); // 경과년수별잔가율(상단)
$('#elpsYrRdvlrt').val(rowData.elpsYrRdvlrt); // 경과년수별잔가율(하단)
$('#bscsCstrnRt').val(''); // 기초공사율
$('#bscsCstrnSeCd').val(''); // 기초공사구분
// 기초공사율 계산을 위한 기준 비율들 초기화 (hidden으로)
if (!$('#bscsCstrnYBdstCmpttnRt').length) {
$('<input type="hidden" id="bscsCstrnYBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="bscsCstrnNBdstCmpttnRt">').appendTo('body');
$('<input type="hidden" id="dupEtbldgBdstCmpttnRt">').appendTo('body');
}
$('#bscsCstrnYBdstCmpttnRt').val('');
$('#bscsCstrnNBdstCmpttnRt').val('');
$('#dupEtbldgBdstCmpttnRt').val('');
$('#bscsCstrnSeCd').trigger('change'); // 기초공사구분 변경 이벤트 트리거
$('#vltnArea').val(vltnArea).trigger('focus').trigger('change'); // 위반면적, change 이벤트 트리거
// 행위유형에 따른 산정률1 설정
$('#cmpttnRtDisplay').val(rowData.actCmpttnRt+ ' %'); // 산정률 표시 필드 (퍼센트 포함)
$('#cmpttnRtCd').val(rowData.actTypeCd); // 산정률 코드 설정
$('#cmpttnRt').val(rowData.actCmpttnRt); // 산정률 값 설정
$('#cmpttnRtRate').val(rowData.actCmpttnRtRate); // 산정률 계산용 비율값 설정
$('#cmpttnRt2Cd').val('').trigger('change'); // 산정률2 변경 이벤트 트리거
// 계산하기 버튼 표시 (값이 변경된 경우)
LevyPrvntcPopup.showCalculateButton(); // 신규 행위정보 선택 시 계산하기 버튼 표시
};
@ -1168,47 +1294,166 @@
},
/**
* 폼 유효성 검증
* 중요로직: 필수 입력 필드 및 데이터 유효성을 검증하여 저장 가능 여부를 판단합니다.
* @returns {boolean} 유효성 검증 결과
* 공통 필드 유효성 검증 함수
* 중요로직: 필드별 검증 규칙을 통합 관리하여 중복 코드를 제거합니다.
* @param {string} validationType - 검증 타입 ('save' 또는 'calculation')
* @returns {Object} 검증 결과 객체 { isValid: boolean, message: string, focusElement: string }
*/
validateForm: function() {
// 필수 필드 검증
if (!$('#actInfoId').val()) {
alert('행위정보를 선택해주세요.');
return false;
validateCommon: function(validationType) {
// 1. 행위정보 선택 검증 (저장 시에만)
if (validationType === 'save' && !$('#actInfoId').val()) {
return {
isValid: false,
message: '행위정보를 선택해주세요.',
focusElement: null
};
}
if (!$('#bscsCstrnSeCd').val()) {
$('#bscsCstrnSeCd').focus();
alert('기초공사구분을 선택해주세요.');
return false;
// 2. 기초공사구분 검증 (저장 시에만)
if (validationType === 'save' && !$('#bscsCstrnSeCd').val()) {
return {
isValid: false,
message: '기초공사율을 선택해주세요.',
focusElement: '#bscsCstrnSeCd'
};
}
// 산정률2 유효성 검증
if (!$('#cmpttnRt2Cd').val()) {
alert('산정률2를 선택해주세요.');
$('#cmpttnRt2Cd').focus();
return false;
// 3. 건물기준시가액 검증 (계산 시에만)
if (validationType === 'calculation') {
var bldgNewPrcCrtrAmt = $('#bldgNewPrcCrtrAmt').inputmask('unmaskedvalue') || '0';
if (!bldgNewPrcCrtrAmt || parseFloat(bldgNewPrcCrtrAmt) <= 0) {
return {
isValid: false,
message: '건물기준시가액을 입력해주세요.',
focusElement: '#bldgNewPrcCrtrAmt'
};
}
}
var vltnArea = parseFloat($('#vltnArea').inputmask('unmaskedvalue')) || 0;
if (vltnArea <= 0) {
alert('위반면적을 입력해주세요.');
$('#vltnArea').focus();
return false;
// 4. 구조지수 검증 (계산 시에만)
if (validationType === 'calculation') {
var strctIdx = $('#strctIdx').inputmask('unmaskedvalue') || '0';
if (!strctIdx || parseFloat(strctIdx) <= 0) {
return {
isValid: false,
message: '구조지수를 입력해주세요.',
focusElement: '#strctIdx'
};
}
}
// 5. 용도지수 검증 (계산 시에만)
if (validationType === 'calculation') {
var usgIdx = $('#usgIdx').inputmask('unmaskedvalue') || '0';
if (!usgIdx || parseFloat(usgIdx) <= 0) {
return {
isValid: false,
message: '용도지수를 입력해주세요.',
focusElement: '#usgIdx'
};
}
}
// 6. 위치지수 검증 (계산 시에만)
if (validationType === 'calculation') {
var pstnIdx = $('#pstnIdx').inputmask('unmaskedvalue') || '0';
if (!pstnIdx || parseFloat(pstnIdx) <= 0) {
return {
isValid: false,
message: '위치지수를 입력해주세요.',
focusElement: '#pstnIdx'
};
}
}
// 7. 경과년수별잔가율 검증 (계산 시에만)
if (validationType === 'calculation') {
var elpsYrRdvlrt = $('#elpsYrRdvlrt').inputmask('unmaskedvalue') || '0';
if (!elpsYrRdvlrt || parseFloat(elpsYrRdvlrt) <= 0) {
return {
isValid: false,
message: '경과년수별잔가율을 입력해주세요.',
focusElement: '#elpsYrRdvlrt'
};
}
}
// 8. 기초공사율 검증 (계산 시에만)
if (validationType === 'calculation') {
var bscsCstrnRt = $('#bscsCstrnRt').inputmask('unmaskedvalue') || '0';
if (!bscsCstrnRt || parseFloat(bscsCstrnRt) <= 0) {
return {
isValid: false,
message: '기초공사율을 선택해주세요.',
focusElement: '#bscsCstrnSeCd'
};
}
}
// 9. 위반면적 검증 (공통)
var vltnArea = $('#vltnArea').inputmask('unmaskedvalue') || '0';
if (!vltnArea || parseFloat(vltnArea) <= 0) {
return {
isValid: false,
message: '위반면적을 입력해주세요.',
focusElement: '#vltnArea'
};
}
// 10. 산정률 검증 (공통 - 계산 시 또는 저장 시 체크)
var cmpttnRtRate = $('#cmpttnRtRate').val();
if (!cmpttnRtRate || parseFloat(cmpttnRtRate) <= 0) {
return {
isValid: false,
message: '산정률을 선택해주세요.',
focusElement: '#cmpttnRtCd'
};
}
// 11. 산정률2 검증 (공통)
var cmpttnRt2Rate = $('#cmpttnRt2Rate').val();
if (!cmpttnRt2Rate || parseFloat(cmpttnRt2Rate) <= 0) {
return {
isValid: false,
message: '산정률2를 선택해주세요.',
focusElement: '#cmpttnRt2Cd'
};
}
// 12. 계산 완료 여부 검증 (저장 시에만)
if (validationType === 'save') {
var taxableMarketPrice = $("#taxableMarketPrice").val(); // 건축물과세시가
var standardMarketPrice = $("#standardMarketPrice").val(); // 시가표준액
var standardMarketPrice_bottom = $("#standardMarketPrice_bottom").val(); // 시가표준액(하단)
var cmpttnAmt = $("#cmpttnAmt").val(); // 산정액
var levyWholAmt = $("#levyWholAmt").val(); // 부과총액
if (!(taxableMarketPrice && standardMarketPrice && standardMarketPrice_bottom && cmpttnAmt && levyWholAmt)) {
return {
isValid: false,
message: '[계산하기] 버튼을 클릭 하여 계산을 완료하시기 바랍니다.',
focusElement: null
};
}
}
var taxableMarketPrice = $("#taxableMarketPrice").val();
var standardMarketPrice = $("#standardMarketPrice").val();
var standardMarketPrice_bottom = $("#standardMarketPrice_bottom").val();
var cmpttnAmt = $("#cmpttnAmt").val();
var levyWholAmt = $("#levyWholAmt").val();
if( !(taxableMarketPrice && standardMarketPrice && standardMarketPrice_bottom && cmpttnAmt && levyWholAmt) ){
alert('[계산하기] 버튼을 클릭 하여 계산을 완료하시기 바랍니다.');
return { isValid: true }; // 모든 검증 통과
},
/**
* 폼 유효성 검증 (저장용)
* 중요로직: 저장 시 필요한 모든 필드를 검증합니다.
* @returns {boolean} 유효성 검증 결과
*/
validateForm: function() {
var result = this.validateCommon('save'); // 공통 검증 함수 호출
if (!result.isValid) {
alert(result.message); // 오류 메시지 표시
if (result.focusElement) {
$(result.focusElement).focus(); // 문제 필드로 포커스 이동
}
return false;
}
return true;
},
@ -1425,96 +1670,12 @@
},
/**
* 통합 계산 필수값 검증 함수
* 중요로직: 건축물과세시가 계산부터 부과총액까지 전체 계산에 필요한 모든 필수값을 검증합니다.
* 통합 계산 필수값 검증 함수 (계산용)
* 중요로직: 계산 시 필요한 모든 필드를 검증합니다.
* @returns {Object} 검증 결과 객체 { isValid: boolean, message: string, focusElement: string }
*/
validateCalculationInputs: function() {
// 1. 건축물과세시가 계산에 필요한 값들 검증
var bldgNewPrcCrtrAmt = $('#bldgNewPrcCrtrAmt').inputmask('unmaskedvalue') || '0'; // 건물기준시가액
if (!bldgNewPrcCrtrAmt || parseFloat(bldgNewPrcCrtrAmt) <= 0) {
return {
isValid: false,
message: '건물기준시가액을 입력해주세요.',
focusElement: '#bldgNewPrcCrtrAmt'
};
}
var strctIdx = $('#strctIdx').inputmask('unmaskedvalue') || '0'; // 구조지수
if (!strctIdx || parseFloat(strctIdx) <= 0) {
return {
isValid: false,
message: '구조지수를 입력해주세요.',
focusElement: '#strctIdx'
};
}
var usgIdx = $('#usgIdx').inputmask('unmaskedvalue') || '0'; // 용도지수
if (!usgIdx || parseFloat(usgIdx) <= 0) {
return {
isValid: false,
message: '용도지수를 입력해주세요.',
focusElement: '#usgIdx'
};
}
var pstnIdx = $('#pstnIdx').inputmask('unmaskedvalue') || '0'; // 위치지수
if (!pstnIdx || parseFloat(pstnIdx) <= 0) {
return {
isValid: false,
message: '위치지수를 입력해주세요.',
focusElement: '#pstnIdx'
};
}
var elpsYrRdvlrt = $('#elpsYrRdvlrt').inputmask('unmaskedvalue') || '0'; // 경과년수별잔가율
if (!elpsYrRdvlrt || parseFloat(elpsYrRdvlrt) <= 0) {
return {
isValid: false,
message: '경과년수별잔가율을 입력해주세요.',
focusElement: '#elpsYrRdvlrt'
};
}
var bscsCstrnRt = $('#bscsCstrnRt').inputmask('unmaskedvalue') || '0'; // 기초공사율
if (!bscsCstrnRt || parseFloat(bscsCstrnRt) <= 0) {
return {
isValid: false,
message: '기초공사율을 선택해주세요.',
focusElement: '#bscsCstrnSeCd'
};
}
// 2. 위반면적 검증
var vltnArea = $('#vltnArea').inputmask('unmaskedvalue') || '0'; // 위반면적
if (!vltnArea || parseFloat(vltnArea) <= 0) {
return {
isValid: false,
message: '위반면적을 입력해주세요.',
focusElement: '#vltnArea'
};
}
// 3. 산정률 검증
var cmpttnRtRate = $('#cmpttnRtRate').val(); // 산정률 비율값
if (!cmpttnRtRate || parseFloat(cmpttnRtRate) <= 0) {
return {
isValid: false,
message: '산정률을 선택해주세요.',
focusElement: '#cmpttnRtCd'
};
}
// 4. 산정률2 검증
var cmpttnRt2Rate = $('#cmpttnRt2Rate').val(); // 산정률2 비율값
if (!cmpttnRt2Rate || parseFloat(cmpttnRt2Rate) <= 0) {
return {
isValid: false,
message: '산정률2를 선택해주세요.',
focusElement: '#cmpttnRt2Cd'
};
}
return { isValid: true }; // 모든 검증 통과
return this.validateCommon('calculation'); // 공통 검증 함수 호출
},
/**

@ -49,19 +49,27 @@ function validateFormByAttributes(formId) {
// validation-check 속성을 가진 모든 요소 선택
const elements = form.querySelectorAll('[validation-check]');
let isValid = true;
let focusTarget = null;
// 각 요소에 대해 검증 수행
elements.forEach(element => {
const elementValid = validateByAttribute(element);
isValid = isValid && elementValid;
if( !elementValid ){
focusTarget = element;
}
});
// 별도 함수로 진행된 validation 중에 is-invalid class 가 포함되어 있으면 취소
elements.forEach(element => {
if( element.classList.contains('is-invalid-custom') ){
isValid = false;
focusTarget = element;
}
});
if( focusTarget ){
focusTarget.focus();
}
return isValid;
}
@ -130,7 +138,6 @@ function validateByAttribute(element) {
switch (type) {
case 'required':
if (isEmpty(value)) {
console.log(value, isEmpty(value));
isValid = false;
errorMessage = '필수 입력 항목입니다.';
}

Loading…
Cancel
Save