diff --git a/DB-DDL/maria/ddl/ibmsdb/tb_actn_info.sql b/DB-DDL/maria/ddl/ibmsdb/tb_actn_info.sql index 7ea974b..e60faed 100644 --- a/DB-DDL/maria/ddl/ibmsdb/tb_actn_info.sql +++ b/DB-DDL/maria/ddl/ibmsdb/tb_actn_info.sql @@ -18,3 +18,4 @@ create table tb_actn_info DLTR varchar(11) null comment '삭제자' ) comment '조치 정보'; + diff --git a/DB-DDL/maria/ddl/ibmsdb/tb_crdn_photo.sql b/DB-DDL/maria/ddl/ibmsdb/tb_crdn_photo.sql index 0f7ecea..ab3ba41 100644 --- a/DB-DDL/maria/ddl/ibmsdb/tb_crdn_photo.sql +++ b/DB-DDL/maria/ddl/ibmsdb/tb_crdn_photo.sql @@ -8,13 +8,13 @@ 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 '등록자', DEL_YN char not null comment '삭제 여부', DEL_DT datetime null comment '삭제 일시', DLTR varchar(11) null comment '삭제자', + ACTN_INFO_ID varchar(10) null comment '조치 정보 ID', primary key (ACT_INFO_ID, CRDN_PHOTO_SN) ) comment '단속 사진'; diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/controller/CrdnActInfoController.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/controller/CrdnActInfoController.java index 6029d06..cc74347 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/controller/CrdnActInfoController.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/controller/CrdnActInfoController.java @@ -29,7 +29,9 @@ import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * 불법위반행위정보 컨트롤러 @@ -325,6 +327,7 @@ public class CrdnActInfoController { @GetMapping("/photoView.do") public String photoView( @Parameter(description = "행위정보ID") @RequestParam String actInfoId, + @Parameter(description = "조치정보ID") @RequestParam(required = false) String actnInfoId, @Parameter(description = "사진순번") @RequestParam String crdnPhotoSn, @Parameter(description = "단속조치사진구분") @RequestParam String crdnPhotoSeCd, Model model) { @@ -334,6 +337,7 @@ public class CrdnActInfoController { // 중요한 로직 주석: 해당 행위정보의 모든 사진 목록 조회 CrdnPhotoVO searchVO = CrdnPhotoVO.builder() .actInfoId(actInfoId) + .actnInfoId(actnInfoId) .crdnPhotoSeCd(crdnPhotoSeCd) // 단속 사진 .build(); @@ -433,4 +437,229 @@ public class CrdnActInfoController { return ApiResponseUtil.error("사진 삭제에 실패했습니다."); } } + + // ======================================= 여기서부터 조치 정보 ========================================== + + /** + * 조치정보 관리 팝업 (View) + * 중요한 로직 주석: 조치정보를 관리하는 별도 팝업 페이지를 반환한다. + * @param actInfoId 행위정보 ID + * @param crdnYr 단속연도 + * @param crdnNo 단속번호 + * @param mode 화면모드 + * @param model 뷰에 전달할 데이터 모델 + * @return 조치정보 관리 JSP 페이지 + */ + @Operation(summary = "조치정보 관리 팝업", description = "조치정보 관리 팝업을 반환합니다.") + @GetMapping("/crdnActnInfoRegistPopup.do") + public ModelAndView actnInfoManage( + @Parameter(description = "행위정보ID") @RequestParam String actInfoId, + @Parameter(description = "단속연도") @RequestParam String crdnYr, + @Parameter(description = "단속번호") @RequestParam String crdnNo, + @Parameter(description = "위치정보ID") @RequestParam String pstnInfoId, + @Parameter(description = "화면모드") @RequestParam(required = false, defaultValue = "U") String mode, + Model model) { + + log.debug("조치정보 관리 팝업 요청 - actInfoId: {}, crdnYr: {}, crdnNo: {}, mode: {}", actInfoId, crdnYr, crdnNo, mode); + + ModelAndView mav = new ModelAndView("crdn/crndRegistAndView/crdnActInfo/crdnActnInfoRegistPopup" + TilesConstants.POPUP); + mav.addObject("mode", mode); + mav.addObject("actInfoId", actInfoId); + mav.addObject("pstnInfoId", pstnInfoId); + mav.addObject("crdnYr", crdnYr); + mav.addObject("crdnNo", crdnNo); + + return mav; + } + + /** + * 조치정보 목록 조회 (AJAX) + * 중요한 로직 주석: 특정 행위정보에 속한 조치정보 목록을 페이징 처리하여 조회한다. + * @param vo 조회 조건 (actInfoId 포함) + * @return 조치정보 목록과 페이징 정보 + */ + @Operation(summary = "조치정보 목록 조회", description = "특정 행위정보에 속한 조치정보 목록을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/actnInfoList.ajax") + public ResponseEntity actnInfoListAjax(@ModelAttribute CrdnActnInfoVO vo) { + log.debug("조치정보 목록 조회 요청: {}", vo); + + // 1. 총 개수 조회 + int totalCount = service.selectActnInfoListTotalCount(vo); + // 2. 응답 데이터 구성 + vo.setTotalCount(totalCount); + + List list = service.selectActnInfoList(vo); + return ApiResponseUtil.successWithGrid(list, vo); + } + + /** + * 조치정보 상세 조회 (AJAX) + * 중요한 로직 주석: 조치정보 ID로 단건 조회한다. + * @param actnInfoId 조치정보 ID + * @return 조치정보 상세 데이터 + */ + @Operation(summary = "조치정보 상세 조회", description = "조치정보 ID로 상세 정보를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/actnInfoDetail.ajax") + public ResponseEntity actnInfoDetailAjax(@RequestParam String actnInfoId) { + log.debug("조치정보 상세 조회 요청: {}", actnInfoId); + + CrdnActnInfoVO result = service.selectActnInfoByPk(actnInfoId); + if (result != null) { + return ApiResponseUtil.success(result); + } else { + return ApiResponseUtil.error("조치정보를 찾을 수 없습니다."); + } + } + + /** + * 조치정보 등록 (AJAX) + * 중요한 로직 주석: 조치정보와 조치사진을 함께 등록한다. + * @param vo 조치정보 데이터 + * @param actnPhotoFiles 조치사진 파일 목록 (선택사항) + * @return 등록 결과 + */ + @Operation(summary = "조치정보 저장", description = "조치정보를 등록합니다. 조치사진도 함께 처리 가능합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "등록 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/saveActnInfo.ajax") + public ResponseEntity actnInfoInsertAjax( + @ModelAttribute CrdnActnInfoVO vo, + @RequestParam(value = "actnPhotoFiles", required = false) List actnPhotoFiles) { + + log.debug("조치정보 등록 요청: {}, 조치사진 개수: {}", + vo, actnPhotoFiles != null ? actnPhotoFiles.size() : 0); + + CrdnRegistAndViewVO crdnVO = new CrdnRegistAndViewVO(); + crdnVO.setCrdnYr(vo.getCrdnYr()); + crdnVO.setCrdnNo(vo.getCrdnNo()); + CrdnRegistAndViewVO data = crdnRegistAndViewService.selectOne(crdnVO); + + vo.setSggCd(data.getSggCd()); + vo.setRgtr(SessionUtil.getUserId()); + vo.setMdfr(SessionUtil.getUserId()); + + int result = 0; + if( vo.getActnInfoId() == null || vo.getActnInfoId().isEmpty() ){ + //등록 + result = service.insertActnInfoWithFiles(vo, actnPhotoFiles); + } else{ + result = service.updateActnInfoWithFiles(vo, actnPhotoFiles); + } + + if (result > 0) { + return ApiResponseUtil.success("조치정보가 성공적으로 저장되었습니다."); + } else { + return ApiResponseUtil.error("조치정보 저장에 실패했습니다."); + } + } + + + /** + * 조치정보 삭제 (AJAX) + * 중요한 로직 주석: 조치정보와 관련 조치사진을 논리삭제한다. + * @param actnInfoIds 삭제할 조치정보 ID 목록 + * @return 삭제 결과 + */ + @Operation(summary = "조치정보 삭제", description = "조치정보를 삭제합니다. 관련된 조치사진도 함께 삭제됩니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "삭제 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/deleteActnInfo.ajax") + public ResponseEntity actnInfoDeleteAjax(@RequestParam List actnInfoIds) { + log.debug("조치정보 삭제 요청: {}", actnInfoIds); + + int result = service.deleteActnInfos(actnInfoIds); + + if (result > 0) { + return ApiResponseUtil.success("조치정보 " + result + "건이 성공적으로 삭제되었습니다."); + } else { + return ApiResponseUtil.error("조치정보 삭제에 실패했습니다."); + } + } + + /** + * 조치사진 목록 조회 (AJAX) + * 중요한 로직 주석: 특정 조치정보에 속한 조치사진 목록을 조회한다. + * @param actnInfoId 조치정보 ID + * @return 조치사진 목록 + */ + @Operation(summary = "조치사진 목록 조회", description = "특정 조치정보에 속한 조치사진 목록을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/selectActnPhotos.ajax") + public ResponseEntity selectActnPhotosAjax(@RequestParam String actnInfoId) { + log.debug("조치사진 목록 조회 요청: {}", actnInfoId); + + try { + // 중요한 로직 주석: 조치사진 조회 조건 생성 (CRDN_PHOTO_SE_CD = '2': 조치사진) + CrdnPhotoVO photoVO = CrdnPhotoVO.builder() + .actnInfoId(actnInfoId) // 조치정보 ID + .crdnPhotoSeCd("2") // 조치사진 구분코드 + .build(); + + List photoList = photoService.selectPhotoList(photoVO); + + return ApiResponseUtil.success(photoList); + + } catch (Exception e) { + log.error("조치사진 목록 조회 중 오류 발생: {}", actnInfoId, e); + return ApiResponseUtil.error("조치사진 목록 조회에 실패했습니다: " + e.getMessage()); + } + } + + /** + * 조치사진 개별 삭제 (AJAX) + * 중요한 로직 주석: 조치사진을 개별적으로 삭제하는 Ajax 엔드포인트로, DB 논리삭제 후 실제 파일도 삭제한다. + * @param actnInfoId 조치정보 ID + * @param crdnPhotoSn 사진순번 + * @return 삭제 결과 + */ + @Operation(summary = "조치사진 개별 삭제", description = "조치사진을 개별적으로 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "삭제 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/deleteActnPhoto.ajax") + public ResponseEntity deleteActnPhotoAjax( + @Parameter(description = "조치정보ID") @RequestParam String actnInfoId, + @Parameter(description = "사진순번") @RequestParam String crdnPhotoSn) { + + log.debug("조치사진 개별 삭제 요청 - actnInfoId: {}, crdnPhotoSn: {}", actnInfoId, crdnPhotoSn); + + try { + // 중요한 로직 주석: 삭제할 조치사진 정보 생성 + CrdnPhotoVO photoVO = CrdnPhotoVO.builder() + .actnInfoId(actnInfoId) // 조치정보 ID + .crdnPhotoSn(crdnPhotoSn) + .dltr(SessionUtil.getUserId()) + .build(); + + // 중요한 로직 주석: 조치사진 삭제 (논리삭제 + 파일삭제) + int result = photoService.deletePhotoWithFile(photoVO); + + if (result > 0) { + return ApiResponseUtil.success("조치사진이 성공적으로 삭제되었습니다."); + } else { + return ApiResponseUtil.error("조치사진 삭제에 실패했습니다."); + } + + } catch (Exception e) { + log.error("조치사진 삭제 중 오류 발생: actnInfoId={}, crdnPhotoSn={}", actnInfoId, crdnPhotoSn, e); + return ApiResponseUtil.error("조치사진 삭제 중 오류가 발생했습니다: " + e.getMessage()); + } + } + } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnActInfoMapper.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnActInfoMapper.java index 05368b9..78b3217 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnActInfoMapper.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnActInfoMapper.java @@ -90,4 +90,48 @@ public interface CrdnActInfoMapper { int existsLevyInfoList(List pstnInfoIds); + // ================================ 조치정보 관련 메서드 ================================ + + /** + * 조치정보 목록 조회 + * @param vo 검색조건 (actInfoId 포함) + * @return 조치정보 목록 + */ + List selectActnInfoList(CrdnActnInfoVO vo); + + /** + * 조치정보 목록 총 개수 조회 + * @param vo 검색조건 (actInfoId 포함) + * @return 총 개수 + */ + int selectActnInfoListTotalCount(CrdnActnInfoVO vo); + + /** + * 조치정보 상세 조회 (PK 기준) + * @param actnInfoId 조치정보 ID + * @return 조치정보 상세 + */ + CrdnActnInfoVO selectActnInfoByPk(String actnInfoId); + + /** + * 조치정보 등록 + * @param vo 등록할 조치정보 + * @return 등록 결과 + */ + int insertActnInfo(CrdnActnInfoVO vo); + + /** + * 조치정보 수정 + * @param vo 수정할 조치정보 + * @return 수정 결과 + */ + int updateActnInfo(CrdnActnInfoVO vo); + + /** + * 조치정보 삭제 (논리삭제) + * @param vo 삭제할 조치정보 (actnInfoId, dltr 포함) + * @return 삭제 결과 + */ + int deleteActnInfo(CrdnActnInfoVO vo); + } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnPhotoMapper.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnPhotoMapper.java index 3b112c5..dc35602 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnPhotoMapper.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/mapper/CrdnPhotoMapper.java @@ -77,4 +77,14 @@ public interface CrdnPhotoMapper { * @return 존재 여부 (1: 존재, 0: 없음) */ int existsPhoto(CrdnPhotoVO photoVO); + + // ================================ 조치사진 관련 메서드 ================================ + + /** + * 조치정보별 조치사진 목록 조회 + * 중요한 로직 주석: AACTN_INFO_ID를 기준으로 조치사진을 조회한다. + * @param photoVO 조치정보 ID 포함 (actnInfoId) + * @return 조치사진 목록 + */ + List selectPhotoListByActnInfoId(CrdnPhotoVO photoVO); } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnActnInfoVO.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnActnInfoVO.java new file mode 100644 index 0000000..657f8c7 --- /dev/null +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnActnInfoVO.java @@ -0,0 +1,56 @@ +package go.kr.project.crdn.crndRegistAndView.crdnActInfo.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import go.kr.project.common.model.PagingVO; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 조치정보 VO + * 중요한 로직 주석: tb_actn_info 테이블과 매핑되며, 불법행위정보에 대한 조치 내역을 관리한다. + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CrdnActnInfoVO extends PagingVO { + + // ============ 테이블 컬럼 ============ + private String actnInfoId; // 조치 정보 ID (PK) + private String sggCd; // 시군구 코드 + private String crdnYr; // 단속 연도 + private String crdnNo; // 단속 번호 + private String actInfoId; // 행위 정보 ID (FK - tb_act_info) + private String actnYmd; // 조치 일자 (YYYYMMDD) + private BigDecimal actnArea; // 조치 면적 + private String actnRmrk; // 조치 비고 + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private LocalDateTime regDt; // 등록 일시 + private String rgtr; // 등록자 + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private LocalDateTime mdfcnDt; // 수정 일시 + private String mdfr; // 수정자 + + private String delYn; // 삭제 여부 + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + private LocalDateTime delDt; // 삭제 일시 + private String dltr; // 삭제자 + + // ============ 조회용 추가 필드 ============ + private int rownum; // 행 번호 (목록 조회용) + + // ============ 검색 조건 필드 ============ + private String searchActInfoId; // 검색용 행위정보 ID + private String searchStartYmd; // 검색 시작일자 + private String searchEndYmd; // 검색 종료일자 +} \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnPhotoVO.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnPhotoVO.java index 010a50b..6035889 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnPhotoVO.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/model/CrdnPhotoVO.java @@ -30,6 +30,7 @@ public class CrdnPhotoVO { private String crdnPhotoNm; // 단속 사진 명 private String crdnPhotoSeCd; // 단속 사진 구분 코드 (01:단속, 02:조치) private String orgnlPhotoNm; // 원본 사진 명 + private String actnInfoId; // 조치정보ID @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnActInfoService.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnActInfoService.java index 93437ed..ee04359 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnActInfoService.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnActInfoService.java @@ -96,4 +96,61 @@ public interface CrdnActInfoService { */ List getAllPstnIdx(); + // ================================ 조치정보 관련 메서드 ================================ + + /** + * 조치정보 목록 조회 + * 중요한 로직 주석: 특정 행위정보에 속한 조치정보 목록을 조회한다. + * @param vo 검색조건 (actInfoId 포함) + * @return 조치정보 목록 + */ + List selectActnInfoList(CrdnActnInfoVO vo); + + /** + * 조치정보 목록 총 개수 조회 + * @param vo 검색조건 (actInfoId 포함) + * @return 총 개수 + */ + int selectActnInfoListTotalCount(CrdnActnInfoVO vo); + + /** + * 조치정보 상세 조회 (PK 기준) + * @param actnInfoId 조치정보 ID + * @return 조치정보 상세 + */ + CrdnActnInfoVO selectActnInfoByPk(String actnInfoId); + + /** + * 조치정보 등록 (파일 포함) + * 중요한 로직 주석: 조치정보 등록과 함께 조치사진 파일도 처리한다. 모든 로직은 한 트랜잭션으로 처리된다. + * @param vo 등록할 조치정보 + * @param actnPhotoFiles 업로드할 조치사진 파일 목록 (선택사항) + * @return 등록 결과 + */ + int insertActnInfoWithFiles(CrdnActnInfoVO vo, List actnPhotoFiles); + + /** + * 조치정보 수정 (파일 포함) + * 중요한 로직 주석: 조치정보 수정과 함께 조치사진 파일도 처리한다. 모든 로직은 한 트랜잭션으로 처리된다. + * @param vo 수정할 조치정보 + * @param actnPhotoFiles 업로드할 조치사진 파일 목록 (선택사항) + * @return 수정 결과 + */ + int updateActnInfoWithFiles(CrdnActnInfoVO vo, List actnPhotoFiles); + + /** + * 조치정보 삭제 (논리삭제) + * 중요한 로직 주석: 조치정보와 관련 조치사진을 논리삭제한다. + * @param vo 삭제할 조치정보 (actnInfoId, dltr 포함) + * @return 삭제 결과 + */ + int deleteActnInfo(CrdnActnInfoVO vo); + + /** + * 조치정보 일괄 삭제 (논리삭제) + * @param actnInfoIds 삭제할 조치정보ID 목록 + * @return 삭제된 건수 + */ + int deleteActnInfos(List actnInfoIds); + } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnPhotoService.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnPhotoService.java index bee6814..20bc37f 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnPhotoService.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/CrdnPhotoService.java @@ -1,6 +1,7 @@ package go.kr.project.crdn.crndRegistAndView.crdnActInfo.service; import go.kr.project.common.model.FileVO; +import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActnInfoVO; import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnPhotoVO; import org.springframework.web.multipart.MultipartFile; @@ -93,4 +94,31 @@ public interface CrdnPhotoService { * @return 다음 사진 순번 (01, 02, 03 형태) */ String getNextPhotoSn(String actInfoId); + + // ================================ 조치사진 관련 메서드 ================================ + + /** + * 조치사진 파일 업로드 및 등록 + * 중요한 로직 주석: 조치사진 전용 업로드 메서드로 AACTN_INFO_ID에 조치정보 ID를 저장한다. + * @param files 업로드할 조치사진 파일 목록 + * @param actnInfoVO 조치정보 + * @return 등록된 조치사진 개수 + */ + int insertActnPhotosWithFiles(List files, CrdnActnInfoVO actnInfoVO); + + /** + * 특정 조치정보의 조치사진 목록 조회 + * @param photoVO 조치정보 ID 포함 + * @return 조치사진 목록 + */ + List selectPhotoList(CrdnPhotoVO photoVO); + + /** + * 특정 조치정보의 모든 조치사진 삭제 + * 중요한 로직 주석: 조치정보 삭제 시 관련된 모든 조치사진을 함께 삭제한다. + * @param actnInfoId 조치정보 ID + * @param dltr 삭제자 ID + * @return 삭제된 조치사진 개수 + */ + int deleteActnPhotosByActnInfoId(String actnInfoId, String dltr); } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnActInfoServiceImpl.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnActInfoServiceImpl.java index f6ccb9b..91ca2ff 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnActInfoServiceImpl.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnActInfoServiceImpl.java @@ -263,4 +263,173 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C log.info("불법위반행위정보 일괄 삭제 완료. 요청: {}건, 삭제: {}건", actInfoIds.size(), deletedCount); return deletedCount; } + + // ================================ 조치정보 관련 메서드 구현 ================================ + + @Override + public List selectActnInfoList(CrdnActnInfoVO vo) { + log.debug("조치정보 목록 조회: {}", vo); + return mapper.selectActnInfoList(vo); + } + + @Override + public int selectActnInfoListTotalCount(CrdnActnInfoVO vo) { + log.debug("조치정보 총 개수 조회: {}", vo); + return mapper.selectActnInfoListTotalCount(vo); + } + + @Override + public CrdnActnInfoVO selectActnInfoByPk(String actnInfoId) { + log.debug("조치정보 상세 조회: {}", actnInfoId); + return mapper.selectActnInfoByPk(actnInfoId); + } + + @Override + @Transactional + public int insertActnInfoWithFiles(CrdnActnInfoVO vo, List actnPhotoFiles) { + log.debug("조치정보 등록 (파일 포함): {}, 조치사진 개수: {}", + vo, actnPhotoFiles != null ? actnPhotoFiles.size() : 0); + + try { + // 중요한 로직 주석: 먼저 조치정보를 등록한다 (insertActnInfo 메서드에서 actnInfoId가 자동 설정됨) + int result = mapper.insertActnInfo(vo); + + if (result > 0) { + log.debug("조치정보 등록 완료: actnInfoId={}", vo.getActnInfoId()); + + // 중요한 로직 주석: 조치사진이 있는 경우 처리 (CRDN_PHOTO_SE_CD = '2') + // 조치사진은 AACTN_INFO_ID 컬럼에 조치정보 ID를 저장한다 + if (actnPhotoFiles != null && !actnPhotoFiles.isEmpty()) { + // 중요한 로직 주석: 조치사진을 행위정보에 연결하되, ACTN_INFO_ID에는 조치정보 ID를 저장 + int actnPhotoResult = photoService.insertActnPhotosWithFiles(actnPhotoFiles, vo); + log.debug("조치사진 파일 처리 완료: actnInfoId={}, 등록된 조치사진 수={}", vo.getActnInfoId(), actnPhotoResult); + } + } else { + log.error("조치정보 등록 실패: {}", vo); + throw new MessageException("조치정보 등록에 실패했습니다."); + } + + return result; + + } catch (Exception e) { + log.error("조치정보 등록 (파일 포함) 중 오류 발생: {}", vo, e); + throw new MessageException("조치정보 등록 중 오류가 발생했습니다: " + e.getMessage()); + } + } + + @Override + @Transactional + public int updateActnInfoWithFiles(CrdnActnInfoVO vo, List actnPhotoFiles) { + log.debug("조치정보 수정 (파일 포함): {}, 조치사진 개수: {}", + vo, actnPhotoFiles != null ? actnPhotoFiles.size() : 0); + + try { + // 중요한 로직 주석: 먼저 조치정보를 수정한다 + int result = mapper.updateActnInfo(vo); + + if (result > 0) { + log.debug("조치정보 수정 완료: actnInfoId={}", vo.getActnInfoId()); + + if (actnPhotoFiles != null && !actnPhotoFiles.isEmpty()) { + // 중요한 로직 주석: 조치사진을 행위정보에 연결하되, ACTN_INFO_ID에는 조치정보 ID를 저장 + int actnPhotoResult = photoService.insertActnPhotosWithFiles(actnPhotoFiles, vo); + log.debug("조치사진 파일 처리 완료: actnInfoId={}, 등록된 조치사진 수={}", vo.getActnInfoId(), actnPhotoResult); + } + } else { + log.error("조치정보 수정 실패: {}", vo); + throw new MessageException("조치정보 수정에 실패했습니다."); + } + + return result; + + } catch (Exception e) { + log.error("조치정보 수정 (파일 포함) 중 오류 발생: {}", vo, e); + throw new MessageException("조치정보 수정 중 오류가 발생했습니다: " + e.getMessage()); + } + } + + @Override + @Transactional + public int deleteActnInfo(CrdnActnInfoVO vo) { + log.debug("조치정보 삭제: {}", vo); + + // 중요한 로직 주석: 조치정보 삭제 시 관련된 모든 조치사진도 함께 삭제한다. + if (vo.getActnInfoId() != null) { + try { + // 중요한 로직 주석: 조치사진 삭제 (AACTN_INFO_ID = 조치정보ID, CRDN_PHOTO_SE_CD = '2') + photoService.deleteActnPhotosByActnInfoId(vo.getActnInfoId(), vo.getDltr()); + log.debug("조치정보 관련 조치사진 삭제 완료: actnInfoId={}", vo.getActnInfoId()); + } catch (Exception e) { + log.warn("조치정보 관련 사진 삭제 중 오류 발생: actnInfoId={}", vo.getActnInfoId(), e); + // 사진 삭제 실패해도 조치정보 삭제는 진행 + } + } + + return mapper.deleteActnInfo(vo); + } + + /** + * 조치정보 일괄 삭제 (논리삭제) + * 중요한 로직 주석: 트랜잭션을 적용하여 여러 건의 조치정보 삭제 작업을 안전하게 처리한다. + * 각 ID에 대해 유효성을 검증하고, 세션에서 삭제자 정보를 설정한다. + * @param actnInfoIds 삭제할 조치정보ID 목록 + * @return 삭제된 건수 + */ + @Override + @Transactional + public int deleteActnInfos(List actnInfoIds) { + log.debug("조치정보 일괄 삭제: {}", actnInfoIds); + + // 중요한 로직 주석: 삭제할 ID 목록이 비어있는 경우 처리 중단 + if (actnInfoIds == null || actnInfoIds.isEmpty()) { + log.warn("삭제할 조치정보ID 목록이 비어있습니다."); + return 0; + } + + // 중요한 로직 주석: 세션에서 현재 사용자 정보를 가져와 삭제자로 설정 + String userId = SessionUtil.getUserId(); + if (userId == null || userId.trim().isEmpty()) { + log.error("삭제자 정보를 가져올 수 없습니다."); + throw new MessageException("사용자 정보를 확인할 수 없습니다."); + } + + // 중요한 로직 주석: 각 조치정보 ID에 대해 유효성 검증 후 논리 삭제 수행 + int deletedCount = 0; + for (String actnInfoId : actnInfoIds) { + if (actnInfoId != null && !actnInfoId.trim().isEmpty()) { + // 중요한 로직 주석: 삭제할 데이터 존재 여부 확인 + CrdnActnInfoVO existingData = mapper.selectActnInfoByPk(actnInfoId); + if (existingData == null) { + log.warn("삭제할 조치정보를 찾을 수 없습니다. actnInfoId: {}", actnInfoId); + continue; + } + + // 중요한 로직 주석: 해당 조치정보의 관련 사진들을 먼저 삭제한다. + try { + photoService.deleteActnPhotosByActnInfoId(actnInfoId, userId); + log.debug("조치정보 관련 사진 삭제 완료: actnInfoId={}", actnInfoId); + } catch (Exception e) { + log.warn("조치정보 관련 사진 삭제 중 오류 발생: actnInfoId={}", actnInfoId, e); + // 사진 삭제 실패해도 조치정보 삭제는 진행 + } + + // 중요한 로직 주석: 삭제 VO 생성 및 삭제자 정보 설정 + CrdnActnInfoVO deleteVO = CrdnActnInfoVO.builder() + .actnInfoId(actnInfoId) + .dltr(userId) + .build(); + + int result = mapper.deleteActnInfo(deleteVO); + if (result > 0) { + deletedCount++; + log.debug("조치정보 삭제 완료. actnInfoId: {}", actnInfoId); + } else { + log.warn("조치정보 삭제 실패. actnInfoId: {}", actnInfoId); + } + } + } + + log.info("조치정보 일괄 삭제 완료. 요청: {}건, 삭제: {}건", actnInfoIds.size(), deletedCount); + return deletedCount; + } } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnPhotoServiceImpl.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnPhotoServiceImpl.java index 0f7f408..9061e20 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnPhotoServiceImpl.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/crdnActInfo/service/impl/CrdnPhotoServiceImpl.java @@ -6,6 +6,7 @@ import egovframework.util.SessionUtil; import go.kr.project.common.model.FileVO; import go.kr.project.crdn.crndRegistAndView.crdnActInfo.mapper.CrdnPhotoMapper; import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActInfoVO; +import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActnInfoVO; import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnPhotoVO; import go.kr.project.crdn.crndRegistAndView.crdnActInfo.service.CrdnPhotoService; import lombok.RequiredArgsConstructor; @@ -150,8 +151,7 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd } // 중요한 로직 주석: 새 파일 업로드 - List fileList = Arrays.asList(file); - List uploadedFiles = fileUtil.uploadFiles(fileList, "crdn-act-photo"); + List uploadedFiles = fileUtil.uploadFiles(Arrays.asList(file), "crdn-act-photo"); if (!uploadedFiles.isEmpty()) { FileVO newFileVO = uploadedFiles.get(0); @@ -291,4 +291,140 @@ public class CrdnPhotoServiceImpl extends EgovAbstractServiceImpl implements Crd log.debug("다음 사진 순번 조회: actInfoId={}", actInfoId); return mapper.selectNextPhotoSn(actInfoId); } + + // ================================ 조치사진 관련 메서드 구현 ================================ + + @Override + @Transactional + public int insertActnPhotosWithFiles(List files, CrdnActnInfoVO actnInfoVO) { + log.debug("조치사진 파일 업로드 및 등록: actnInfoId={}, 파일 수={}", files != null ? files.size() : 0); + + if (files == null || files.isEmpty()) { + log.debug("업로드할 조치사진 파일이 없습니다."); + return 0; + } + + try { + int savedCount = 0; + + // 중요한 로직 주석: 각 파일을 순회하며 저장 처리 + for (MultipartFile file : files) { + if (file.isEmpty()) { + log.warn("빈 파일이 포함되어 있어 건너뜁니다: {}", file.getOriginalFilename()); + continue; + } + + try { + // 중요한 로직 주석: FileUtil을 통한 파일 저장 (단일 파일을 리스트로 변환하여 처리) + List singleFileList = Arrays.asList(file); + List uploadedFiles = fileUtil.uploadFiles(singleFileList, "crdn-act-photo"); + + if (uploadedFiles.isEmpty()) { + log.error("파일 저장 실패: {}", file.getOriginalFilename()); + continue; + } + FileVO fileVO = uploadedFiles.get(0); + + // 중요한 로직 주석: 다음 사진 순번 생성 + String nextPhotoSn = getNextPhotoSn(actnInfoVO.getActInfoId()); + + // 중요한 로직 주석: 조치사진 정보 생성 (CRDN_PHOTO_SE_CD = '2', AACTN_INFO_ID에 조치정보 ID 저장) + CrdnPhotoVO photoVO = CrdnPhotoVO.builder() + .actInfoId(actnInfoVO.getActInfoId()) // 부모 행위정보 ID + .crdnPhotoSn(nextPhotoSn) // 사진 순번 + .sggCd(actnInfoVO.getSggCd()) // 시군구 코드 + .crdnYr(actnInfoVO.getCrdnYr()) // 단속 연도 + .crdnNo(actnInfoVO.getCrdnNo()) // 단속 번호 + .crdnPhotoPath(fileVO.getFilePath()) // 파일 경로 + .crdnPhotoNm(fileVO.getStoredFileNm()) // 저장된 파일명 + .crdnPhotoSeCd("2") // 조치사진 구분코드 + .actnInfoId(actnInfoVO.getActnInfoId()) // 조치정보 ID (조치사진 연결용) + .orgnlPhotoNm(fileVO.getOriginalFileNm()) // 원본 파일명 + .rgtr(SessionUtil.getUserId()) // 등록자 + .delYn("N") // 삭제 여부 + .build(); + + int result = mapper.insertPhoto(photoVO); + if (result > 0) { + savedCount++; + log.debug("조치사진 등록 완료: actInfoId={}, photoSn={}, fileName={}", + actnInfoVO.getActInfoId(), nextPhotoSn, file.getOriginalFilename()); + } + + } catch (Exception e) { + log.error("조치사진 파일 처리 중 오류: {}", file.getOriginalFilename(), e); + // 개별 파일 오류는 전체 처리를 중단하지 않음 + } + } + + log.info("조치사진 파일 업로드 완료: actnInfoId={}, 총 파일 수={}, 저장된 파일 수={}", actnInfoVO.getActnInfoId(), files.size(), savedCount); + return savedCount; + + } catch (Exception e) { + log.error("조치사진 파일 업로드 중 오류 발생: actnInfoId={}", actnInfoVO.getActnInfoId(), e); + throw new MessageException("조치사진 업로드에 실패했습니다: " + e.getMessage()); + } + } + + @Override + public List selectPhotoList(CrdnPhotoVO photoVO) { + log.debug("조치사진 목록 조회: {}", photoVO); + return mapper.selectPhotoListByActnInfoId(photoVO); + } + + @Override + @Transactional + public int deleteActnPhotosByActnInfoId(String actnInfoId, String dltr) { + log.debug("조치정보 관련 모든 조치사진 삭제: actnInfoId={}", actnInfoId); + + if (actnInfoId == null || actnInfoId.trim().isEmpty()) { + log.warn("조치정보 ID가 없어서 조치사진을 삭제할 수 없습니다."); + return 0; + } + + try { + // 중요한 로직 주석: 삭제할 조치사진 목록 조회 (ACTN_INFO_ID 기준) + CrdnPhotoVO searchVO = CrdnPhotoVO.builder() + .actnInfoId(actnInfoId) + .crdnPhotoSeCd("2") // 조치사진 + .build(); + + List photoList = mapper.selectPhotoListByActnInfoId(searchVO); + + int deletedCount = 0; + for (CrdnPhotoVO photo : photoList) { + // 중요한 로직 주석: 논리삭제 처리 + CrdnPhotoVO deleteVO = CrdnPhotoVO.builder() + .actInfoId(photo.getActInfoId()) + .crdnPhotoSn(photo.getCrdnPhotoSn()) + .dltr(dltr) + .delYn("Y") + .delDt(LocalDateTime.now()) + .build(); + + int result = mapper.deletePhoto(deleteVO); + if (result > 0) { + // 중요한 로직 주석: 실제 파일도 삭제 + try { + FileVO fileVO = new FileVO(); + fileVO.setFilePath(photo.getCrdnPhotoPath()); + fileVO.setStoredFileNm(photo.getCrdnPhotoNm()); + fileVO.setOriginalFileNm(photo.getOrgnlPhotoNm()); + fileUtil.deleteFile(fileVO); + log.debug("조치사진 파일 삭제 완료: {}", photo.getCrdnPhotoPath()); + } catch (Exception e) { + log.warn("조치사진 파일 삭제 실패 (DB는 삭제됨): {}", photo.getCrdnPhotoPath(), e); + } + deletedCount++; + } + } + + log.info("조치정보 관련 모든 조치사진 삭제 완료: actnInfoId={}, 삭제된 사진 수={}", actnInfoId, deletedCount); + return deletedCount; + + } catch (Exception e) { + log.error("조치사진 일괄 삭제 중 오류 발생: actnInfoId={}", actnInfoId, e); + throw new MessageException("조치사진 삭제에 실패했습니다: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnRegistAndViewVO.java b/src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnRegistAndViewVO.java index 21420df..f1e1845 100644 --- a/src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnRegistAndViewVO.java +++ b/src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnRegistAndViewVO.java @@ -107,6 +107,12 @@ public class CrdnRegistAndViewVO extends PagingVO { /* 단속 처리 일자 */ private String crdnPrcsYmd; + + /* 행위 유형 코드 */ + private String actTypeCd; + + /* 용도 지수 코드 */ + private String usgIdxCd; /** 등록 일시 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @@ -150,6 +156,15 @@ public class CrdnRegistAndViewVO extends PagingVO { /** 소유자명들 (쉼표로 구분) */ private String ownrNams; + + /** 행위자명들 (쉼표로 구분) */ + private String actrNams; + + /** 행위 유형 코드명 */ + private String actTypeCdNm; + + /** 용도 지수 코드명 */ + private String usgIdxCdNm; /** 지번 전체 주소 */ private String lotnoWholAddr; @@ -159,27 +174,7 @@ public class CrdnRegistAndViewVO extends PagingVO { /** 역순 행 번호 (그리드 표시용) */ private Integer rowNum; - - /* 사전처분 시작일 */ - private String impltBgngYmd; - /* 사전처분 종료일 */ - private String impltEndYmd; - /* 사전처분 상태 */ - private String impltTaskSeCd; - - /* 시정명령 시작일 */ - private String impltBgngYmd2; - /* 시정명령 종료일 */ - private String impltEndYmd2; - /* 시정명령 상태 */ - private String impltTaskSeCd2; - - /* 시정촉구 시작일 */ - private String impltBgngYmd3; - /* 시정촉구 종료일 */ - private String impltEndYmd3; - /* 시정촉구 상태 */ - private String impltTaskSeCd3; + // ==================== 검색 조건 ==================== diff --git a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnActInfoMapper_maria.xml b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnActInfoMapper_maria.xml index 88189e4..1604b4b 100644 --- a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnActInfoMapper_maria.xml +++ b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnActInfoMapper_maria.xml @@ -339,4 +339,155 @@ AND DEL_YN = 'N' + + + + + + + + + + + + + + /* ActInfoMapper.insertActnInfo : 조치정보 등록 */ + + SELECT LPAD(NEXTVAL(seq_actn_info_id), 10, '0') FROM DUAL + + INSERT INTO tb_actn_info ( + ACTN_INFO_ID, + SGG_CD, + CRDN_YR, + CRDN_NO, + ACT_INFO_ID, + ACTN_YMD, + ACTN_AREA, + ACTN_RMRK, + REG_DT, + RGTR, + MDFCN_DT, + MDFR, + DEL_YN + ) VALUES ( + #{actnInfoId}, + #{sggCd}, + #{crdnYr}, + #{crdnNo}, + #{actInfoId}, + replace(#{actnYmd},'-',''), + #{actnArea}, + #{actnRmrk}, + NOW(), + #{rgtr}, + NOW(), + #{mdfr}, + 'N' + ) + + + + + /* ActInfoMapper.updateActnInfo : 조치정보 수정 */ + UPDATE tb_actn_info SET + ACTN_YMD = replace(#{actnYmd},'-',''), + ACTN_AREA = #{actnArea}, + ACTN_RMRK = #{actnRmrk}, + MDFCN_DT = NOW(), + MDFR = #{mdfr} + WHERE ACTN_INFO_ID = #{actnInfoId} + AND DEL_YN = 'N' + + + + + /* ActInfoMapper.deleteActnInfo : 조치정보 삭제 (논리삭제) */ + UPDATE tb_actn_info SET + DEL_YN = 'Y', + DEL_DT = NOW(), + DLTR = #{dltr} + WHERE ACTN_INFO_ID = #{actnInfoId} + AND DEL_YN = 'N' + + \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnPhotoMapper_maria.xml b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnPhotoMapper_maria.xml index b1ddda1..875fd0a 100644 --- a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnPhotoMapper_maria.xml +++ b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/crdnActInfo/CrdnPhotoMapper_maria.xml @@ -85,6 +85,7 @@ CRDN_PHOTO_NM, CRDN_PHOTO_SE_CD, ORGNL_PHOTO_NM, + ACTN_INFO_ID, REG_DT, RGTR, DEL_YN @@ -98,6 +99,7 @@ #{crdnPhotoNm}, #{crdnPhotoSeCd}, #{orgnlPhotoNm}, + #{actnInfoId}, NOW(), #{rgtr}, 'N' @@ -157,4 +159,44 @@ AND DEL_YN = 'N' + + + + + \ No newline at end of file diff --git a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml index 9032cdd..8b82ce7 100644 --- a/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml +++ b/src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml @@ -7,41 +7,41 @@ @@ -107,7 +124,7 @@ 단속 사진 선택 - 최대 10개, 각 20MB 이하의 이미지 파일만 업로드 가능합니다. + 최대 10개, 각 10MB 이하의 이미지 파일만 업로드 가능합니다.
@@ -134,72 +151,10 @@
- - -
- - - - - - - - - - - - - - - - - - - - - -
조치일자 - - 조치 면적(㎡) - -
비고 - -
조치 사진 - -
-
- - 최대 10개, 각 20MB 이하의 이미지 파일만 업로드 가능합니다. -
-
- - - - -
-
- ${photo.orgnlPhotoNm} -
-
-
${photo.orgnlPhotoNm}
-
[조치]
- -
-
-
-
-
-
-
-
-
- - + + +