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

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

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

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

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

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

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

@ -25,7 +25,9 @@
FROM tb_crdn_photo p 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 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} WHERE p.ACT_INFO_ID = #{actInfoId}
<if test='crdnPhotoSeCd != null and crdnPhotoSeCd != ""'>
AND p.CRDN_PHOTO_SE_CD = #{crdnPhotoSeCd} AND p.CRDN_PHOTO_SE_CD = #{crdnPhotoSeCd}
</if>
AND p.DEL_YN = 'N' AND p.DEL_YN = 'N'
ORDER BY p.CRDN_PHOTO_SN ASC ORDER BY p.CRDN_PHOTO_SN ASC
</select> </select>

@ -45,7 +45,7 @@
pi.PSTN_IDX_NO, -- 위치지수 NO pi.PSTN_IDX_NO, -- 위치지수 NO
pi.PSTN_IDX, -- 위치지수 pi.PSTN_IDX, -- 위치지수
substring(t3.ACT_BGNG_YMD,1,4) as ELPS_YR_RDVLRT_YR, 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-( ELSE 1-(
(CAST(t1.CRDN_YR as SIGNED) - CAST(substring(t3.ACT_BGNG_YMD,1,4) as SIGNED)) (CAST(t1.CRDN_YR as SIGNED) - CAST(substring(t3.ACT_BGNG_YMD,1,4) as SIGNED))
* strct.DPRT * strct.DPRT

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

@ -16,7 +16,7 @@
■ 4단계 계산 프로세스 (서버에서 순차 처리) ■ 4단계 계산 프로세스 (서버에서 순차 처리)
1. 건축물과세시가(bdstTxtnMprc) 계산 - 이부분은 율 이라고 표현되지만 0.85, 1 등 이미지 % 100 처리 된 값으로 표현됨 1. 건축물과세시가(bdstTxtnMprc) 계산 - [경과년수별잔가율, 기초공사율 : 율 이라고 표현되지만 0.85, 1 등, %가 아닌 나누기 100 처리 된 값으로 표현됨]
- 공식: bldgNewPrcCrtrAmt × strctIdx × usgIdx × pstnIdx × (elpsYrRdvlrt) × (bscsCstrnRt) - 공식: bldgNewPrcCrtrAmt × strctIdx × usgIdx × pstnIdx × (elpsYrRdvlrt) × (bscsCstrnRt)
- 설명: 건물기준시가액 × 구조지수 × 용도지수 × 위치지수 × 경과년수별잔가율 × 기초공사율 - 설명: 건물기준시가액 × 구조지수 × 용도지수 × 위치지수 × 경과년수별잔가율 × 기초공사율
- 반올림: setScale(0, RoundingMode.HALF_UP) - 소수점 반올림 - 반올림: 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="op-cell b-right-0 b-bottom-0"></th>
<th class="b-right-0 b-bottom-0">경과년수<br/>별잔가율</th> <th class="b-right-0 b-bottom-0">경과년수<br/>별잔가율</th>
<th class="op-cell 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">기초공율</th> <th class="b-right-0 b-bottom-0">기초공율</th>
<th class="op-cell 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-right-0 b-bottom-0">건축물<br/>과세시가</th>
<th class="b-bottom-0">시가표준액<br/>(1,000원미만절삭)</th> <th class="b-bottom-0">시가표준액<br/>(1,000원미만절삭)</th>
@ -213,7 +337,7 @@
<c:forEach var="code" items="${bscsCstrnSeCdList}"> <c:forEach var="code" items="${bscsCstrnSeCdList}">
<option value="${code.cdId}">${code.cdNm}</option> <option value="${code.cdId}">${code.cdNm}</option>
</c:forEach> </c:forEach>
</select><!-- 기초공율 --> </select><!-- 기초공율 -->
</td> </td>
<td rowspan="2" class="op-cell b-right-0">=</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> <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; LevyPrvntcPopup.levyInfoIdSelect = existingData.levyInfoId;
// 위반면적 설정 // 건축물과세시가
$('#vltnArea').val(existingData.vltnArea || '').trigger('focus'); $('#taxableMarketPrice').val(existingData.bdstTxtnMprc || '').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'); // 건물기준시가액 값 설정
// 구조지수 // 구조지수
$('#strctIdxCd').val(existingData.strctIdxCd || ''); // 구조지수 코드 설정 $('#strctIdxCd').val(existingData.strctIdxCd || ''); // 구조지수 코드 설정
@ -755,11 +864,31 @@
$('#elpsYrRdvlrt_yr').val((existingData.elpsYrRdvlrtYr || '') + ' 년'); // 행위시작년도 $('#elpsYrRdvlrt_yr').val((existingData.elpsYrRdvlrtYr || '') + ' 년'); // 행위시작년도
$('#elpsYrRdvlrt').val(existingData.elpsYrRdvlrt || ''); // 경과년수별잔가율 값 설정 $('#elpsYrRdvlrt').val(existingData.elpsYrRdvlrt || ''); // 경과년수별잔가율 값 설정
// 산정률2 // 기초공사구분 및 비율
$('#cmpttnRt2Cd').val(existingData.cmpttnRt2Cd || ''); // 산정률2 코드 설정 $('#bscsCstrnSeCd').val(existingData.bscsCstrnSeCd || ''); // 기초공사구분 코드 설정
$('#cmpttnRt2').val(existingData.cmpttnRt2 || ''); // 산정률2 값 설정 $('#bscsCstrnRt').val(existingData.bscsCstrnRt || ''); // 기초공사율 값 설정
$('#cmpttnRt2Display').val((existingData.cmpttnRt2 || '') + ' %'); // 산정률2 표시 필드 (퍼센트 포함)
$('#cmpttnRt2Rate').val(existingData.cmpttnRt2Rate || ''); // 계산용 비율값 // 기초공사율 계산을 위한 기준 비율들 저장 (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 || ''); // 가산율 코드 설정 $('#adtnRtCd').val(existingData.adtnRtCd || ''); // 가산율 코드 설정
@ -773,28 +902,6 @@
$('#adsbmtnEnfcRt').val(existingData.adsbmtnEnfcRt || '100'); // 가감산시행령률 값 설정 $('#adsbmtnEnfcRt').val(existingData.adsbmtnEnfcRt || '100'); // 가감산시행령률 값 설정
$('#adsbmtnEnfcRtDisplay').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) { if (existingData.adtnRt && parseFloat(existingData.adtnRt) > 0) {
$('#btnOpenAddPopup').text('+ 가산 [' + existingData.adtnRt + '%]'); $('#btnOpenAddPopup').text('+ 가산 [' + existingData.adtnRt + '%]');
@ -808,6 +915,23 @@
$('#btnOpenMinusPopup').text('- 감산 [0%]'); $('#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(); // 기존 부과정보 로드 시 계산하기 버튼 숨김 LevyPrvntcPopup.hideCalculateButton(); // 기존 부과정보 로드 시 계산하기 버튼 숨김
@ -822,26 +946,6 @@
*/ */
var setActInfoToForm = function(rowData) { var setActInfoToForm = function(rowData) {
var vltnArea = rowData.calcArea; // 면적 - 조치면적 : 위반면적 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) $('#bldgNewPrcCrtrAmtNo').val(rowData.bldgNewPrcCrtrAmtNo); // 건물기준시가액(NO)
$('#bldgNewPrcCrtrAmtDisplay').val(rowData.bdstUsg); // 건물기준시가액(건축물 용도) $('#bldgNewPrcCrtrAmtDisplay').val(rowData.bdstUsg); // 건물기준시가액(건축물 용도)
@ -862,8 +966,30 @@
$('#elpsYrRdvlrt_yr').val(rowData.elpsYrRdvlrtYr+' 년'); // 경과년수별잔가율(상단) $('#elpsYrRdvlrt_yr').val(rowData.elpsYrRdvlrtYr+' 년'); // 경과년수별잔가율(상단)
$('#elpsYrRdvlrt').val(rowData.elpsYrRdvlrt); // 경과년수별잔가율(하단) $('#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'); // 기초공사구분 변경 이벤트 트리거 $('#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(); // 신규 행위정보 선택 시 계산하기 버튼 표시 LevyPrvntcPopup.showCalculateButton(); // 신규 행위정보 선택 시 계산하기 버튼 표시
}; };
@ -1168,47 +1294,166 @@
}, },
/** /**
* 폼 유효성 검증 * 공통 필드 유효성 검증 함수
* 중요로직: 필수 입력 필드 및 데이터 유효성을 검증하여 저장 가능 여부를 판단합니다. * 중요로직: 필드별 검증 규칙을 통합 관리하여 중복 코드를 제거합니다.
* @returns {boolean} 유효성 검증 결과 * @param {string} validationType - 검증 타입 ('save' 또는 'calculation')
* @returns {Object} 검증 결과 객체 { isValid: boolean, message: string, focusElement: string }
*/ */
validateForm: function() { validateCommon: function(validationType) {
// 필수 필드 검증 // 1. 행위정보 선택 검증 (저장 시에만)
if (!$('#actInfoId').val()) { if (validationType === 'save' && !$('#actInfoId').val()) {
alert('행위정보를 선택해주세요.'); return {
return false; isValid: false,
message: '행위정보를 선택해주세요.',
focusElement: null
};
} }
if (!$('#bscsCstrnSeCd').val()) { // 2. 기초공사구분 검증 (저장 시에만)
$('#bscsCstrnSeCd').focus(); if (validationType === 'save' && !$('#bscsCstrnSeCd').val()) {
alert('기초공사구분을 선택해주세요.'); return {
return false; isValid: false,
message: '기초공사율을 선택해주세요.',
focusElement: '#bscsCstrnSeCd'
};
} }
// 산정률2 유효성 검증 // 3. 건물기준시가액 검증 (계산 시에만)
if (!$('#cmpttnRt2Cd').val()) { if (validationType === 'calculation') {
alert('산정률2를 선택해주세요.'); var bldgNewPrcCrtrAmt = $('#bldgNewPrcCrtrAmt').inputmask('unmaskedvalue') || '0';
$('#cmpttnRt2Cd').focus(); if (!bldgNewPrcCrtrAmt || parseFloat(bldgNewPrcCrtrAmt) <= 0) {
return false; return {
isValid: false,
message: '건물기준시가액을 입력해주세요.',
focusElement: '#bldgNewPrcCrtrAmt'
};
}
} }
var vltnArea = parseFloat($('#vltnArea').inputmask('unmaskedvalue')) || 0; // 4. 구조지수 검증 (계산 시에만)
if (vltnArea <= 0) { if (validationType === 'calculation') {
alert('위반면적을 입력해주세요.'); var strctIdx = $('#strctIdx').inputmask('unmaskedvalue') || '0';
$('#vltnArea').focus(); if (!strctIdx || parseFloat(strctIdx) <= 0) {
return false; 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(); return { isValid: true }; // 모든 검증 통과
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('[계산하기] 버튼을 클릭 하여 계산을 완료하시기 바랍니다.'); * @returns {boolean} 유효성 검증 결과
*/
validateForm: function() {
var result = this.validateCommon('save'); // 공통 검증 함수 호출
if (!result.isValid) {
alert(result.message); // 오류 메시지 표시
if (result.focusElement) {
$(result.focusElement).focus(); // 문제 필드로 포커스 이동
}
return false; return false;
} }
return true; return true;
}, },
@ -1425,96 +1670,12 @@
}, },
/** /**
* 통합 계산 필수값 검증 함수 * 통합 계산 필수값 검증 함수 (계산용)
* 중요로직: 건축물과세시가 계산부터 부과총액까지 전체 계산에 필요한 모든 필수값을 검증합니다. * 중요로직: 계산 시 필요한 모든 필드를 검증합니다.
* @returns {Object} 검증 결과 객체 { isValid: boolean, message: string, focusElement: string }
*/ */
validateCalculationInputs: function() { validateCalculationInputs: function() {
// 1. 건축물과세시가 계산에 필요한 값들 검증 return this.validateCommon('calculation'); // 공통 검증 함수 호출
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 }; // 모든 검증 통과
}, },
/** /**

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

Loading…
Cancel
Save