fix: DTO inner class로 변경

main
Jonguk. Lim 2 years ago
parent 13e4a42254
commit 5fdad93642

@ -51,7 +51,7 @@ public class FimsConst {
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum FfnlgCode { public enum SysCode {
PVS("PVS", "주정차위반과태료") PVS("PVS", "주정차위반과태료")
,BPV("BPV", "전용차로위반과태료") ,BPV("BPV", "전용차로위반과태료")
,DPV("DPV", "장애인전용주차구역") ,DPV("DPV", "장애인전용주차구역")

@ -3,7 +3,6 @@ package kr.xit.fims.biz.ec.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -19,7 +18,7 @@ import lombok.ToString;
@EqualsAndHashCode @EqualsAndHashCode
@Builder @Builder
@ToString @ToString
public class EcNatlNewspaperRcvReqDTO implements Serializable { public class NatlNewspaperRcvReqDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String filePath; private String filePath;

@ -24,11 +24,10 @@ import kr.xit.fims.biz.FimsConst;
import kr.xit.fims.biz.ec.mapper.IEcCtznSttemntMapper; import kr.xit.fims.biz.ec.mapper.IEcCtznSttemntMapper;
import kr.xit.fims.biz.ec.mapper.IEcNatlNewspaperMapper; import kr.xit.fims.biz.ec.mapper.IEcNatlNewspaperMapper;
import kr.xit.fims.biz.ec.model.CtznSttemntDTO; import kr.xit.fims.biz.ec.model.CtznSttemntDTO;
import kr.xit.fims.biz.ec.model.EcNatlNewspaperRcvReqDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvReqDTO;
import kr.xit.fims.biz.ec.model.NatlNewspaperRcvDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvDTO;
import kr.xit.fims.biz.ec.model.NatlNewspaperRcvXmlDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvXmlDTO;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
import kr.xit.framework.biz.cmm.service.ICmmFileService; import kr.xit.framework.biz.cmm.service.ICmmFileService;
import kr.xit.framework.support.exception.BizRuntimeException; import kr.xit.framework.support.exception.BizRuntimeException;
import kr.xit.framework.support.util.Checks; import kr.xit.framework.support.util.Checks;
@ -74,7 +73,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Override @Override
@Transactional @Transactional
public void saveEsbRvcParse(final EcNatlNewspaperRcvReqDTO dto) { public void saveEsbRvcParse(final NatlNewspaperRcvReqDTO dto) {
String filePath = dto.getFilePath(); String filePath = dto.getFilePath();
AtomicInteger index = new AtomicInteger(); AtomicInteger index = new AtomicInteger();
// CmmFileMstDTO fstMstDTO = null; // CmmFileMstDTO fstMstDTO = null;
@ -106,8 +105,8 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
ctznSttemntMapper.insertEcCtznSttemnt(ctznSttemntDTO); ctznSttemntMapper.insertEcCtznSttemnt(ctznSttemntDTO);
index.set(0); index.set(0);
CmmFileMstDTO fstMstDTO = null; CmmFileDTO.FileMst fstMstDTO = null;
CmmFileDtlDTO pngDtlDTO = null; CmmFileDTO.FileDtl pngDtlDTO = null;
for(CtznSttemntDTO.CtznSttemntDetailDTO dtl : ctznSttemntDTO.getSttemntDetailDTOs()){ for(CtznSttemntDTO.CtznSttemntDetailDTO dtl : ctznSttemntDTO.getSttemntDetailDTOs()){
ctznSttemntMapper.insertEcCtznSttemntDetail(dtl); ctznSttemntMapper.insertEcCtznSttemntDetail(dtl);
@ -144,7 +143,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
if (fstMstDTO == null || fstMstDTO.getCmmFileDtls().size() == 0) if (fstMstDTO == null || fstMstDTO.getCmmFileDtls().size() == 0)
throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "시민신고 첨부 파일 처리중 오류가 발생하였습니다."); throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "시민신고 첨부 파일 처리중 오류가 발생하였습니다.");
for (CmmFileDtlDTO dtlDto : fstMstDTO.getCmmFileDtls()) { for (CmmFileDTO.FileDtl dtlDto : fstMstDTO.getCmmFileDtls()) {
if (Objects.equals(dtlDto.getFileExtsn(), "png")) { if (Objects.equals(dtlDto.getFileExtsn(), "png")) {
pngDtlDTO = dtlDto; pngDtlDTO = dtlDto;
break; break;
@ -159,7 +158,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
}); });
} }
public void saveCtznSttemnt(final EcNatlNewspaperRcvReqDTO dto){ public void saveCtznSttemnt(final NatlNewspaperRcvReqDTO dto){
} }
@ -183,7 +182,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
dto.setInterfaceSeqN(xmlDto.getInterfaceSeqN()); dto.setInterfaceSeqN(xmlDto.getInterfaceSeqN());
dto.setInsttCode(xmlDto.getAncCodeV()); dto.setInsttCode(xmlDto.getAncCodeV());
//TODO: 재정의 후 적용 //TODO: 재정의 후 적용
dto.setSysCode("88"); dto.setSysCode(FimsConst.SysCode.PVS.getCode());
dto.setCvplSe(xmlDto.getPetiGubunC()); dto.setCvplSe(xmlDto.getPetiGubunC());
dto.setCvplReqstNo(xmlDto.getPetiNoC()); dto.setCvplReqstNo(xmlDto.getPetiNoC());
dto.setCvplRceptNo(xmlDto.getCivilNoC()); dto.setCvplRceptNo(xmlDto.getCivilNoC());
@ -236,7 +235,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
//공통 추가 항목 //공통 추가 항목
dtlDto.setInterfaceSeqN(xmlDto.getInterfaceSeqN()); dtlDto.setInterfaceSeqN(xmlDto.getInterfaceSeqN());
dtlDto.setRegister(""); dtlDto.setRegister(getUserUniqId());
dtlDto.setCtznSttemntDetailProcessSttus(FimsConst.CtznSttemntStatusCode.UNTREATED.getCode()); dtlDto.setCtznSttemntDetailProcessSttus(FimsConst.CtznSttemntStatusCode.UNTREATED.getCode());
dtlList.add(dtlDto); dtlList.add(dtlDto);
isFirst = false; isFirst = false;
@ -338,7 +337,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
} }
private CmmFileMstDTO saveAllAppendFiles( private CmmFileDTO.FileMst saveAllAppendFiles(
NatlNewspaperRcvXmlDTO xmlDto, NatlNewspaperRcvXmlDTO xmlDto,
NatlNewspaperRcvXmlDTO.AppendFileInfoDTO appendFileDto, NatlNewspaperRcvXmlDTO.AppendFileInfoDTO appendFileDto,
CtznSttemntDTO.CtznSttemntDetailDTO sttemntDetailDTO, CtznSttemntDTO.CtznSttemntDetailDTO sttemntDetailDTO,
@ -377,7 +376,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, e.getMessage()); throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, e.getMessage());
} }
} }
CmmFileMstDTO fileMstDTO = new CmmFileMstDTO(); CmmFileDTO.FileMst fileMstDTO = new CmmFileDTO.FileMst();
fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode()); fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode());
fileMstDTO.setFileJobId(sttemntDetailDTO.getInterfaceSeqN()+sttemntDetailDTO.getCtznSttemntDetailSn()); fileMstDTO.setFileJobId(sttemntDetailDTO.getInterfaceSeqN()+sttemntDetailDTO.getCtznSttemntDetailSn());
fileMstDTO.setUploadeJobPath(uploadNewsPaperPath); fileMstDTO.setUploadeJobPath(uploadNewsPaperPath);
@ -391,7 +390,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
//TODO: 단속정보별 파일 저장 하도록 적용 필요 - 2건 단속자료면 1번은 1번 파일만, 2번은 2번 파일만 //TODO: 단속정보별 파일 저장 하도록 적용 필요 - 2건 단속자료면 1번은 1번 파일만, 2번은 2번 파일만
//TODO: 단속정보를 보여줄 때는 모든 이미지 정보 보여준다(insterfaceSeqN 별로 - 어느 DTL항목 파일인지는 당근 표시해야지!!!) //TODO: 단속정보를 보여줄 때는 모든 이미지 정보 보여준다(insterfaceSeqN 별로 - 어느 DTL항목 파일인지는 당근 표시해야지!!!)
//TODO: .png는 각각 set - 파일 업로드는 1번만 //TODO: .png는 각각 set - 파일 업로드는 1번만
private CmmFileMstDTO saveAppendFiles( private CmmFileDTO.FileMst saveAppendFiles(
NatlNewspaperRcvXmlDTO.AppendFileInfoDTO apndFileDTO, NatlNewspaperRcvXmlDTO.AppendFileInfoDTO apndFileDTO,
CtznSttemntDTO.CtznSttemntDetailDTO stmtDtlDTO, CtznSttemntDTO.CtznSttemntDetailDTO stmtDtlDTO,
int index, int index,
@ -448,7 +447,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, e.getMessage()); throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, e.getMessage());
} }
CmmFileMstDTO fileMstDTO = new CmmFileMstDTO(); CmmFileDTO.FileMst fileMstDTO = new CmmFileDTO.FileMst();
fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode()); fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode());
fileMstDTO.setFileJobId(stmtDtlDTO.getInterfaceSeqN()+stmtDtlDTO.getCtznSttemntDetailSn()); fileMstDTO.setFileJobId(stmtDtlDTO.getInterfaceSeqN()+stmtDtlDTO.getCtznSttemntDetailSn());
fileMstDTO.setUploadeJobPath(uploadNewsPaperPath); fileMstDTO.setUploadeJobPath(uploadNewsPaperPath);
@ -457,10 +456,10 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
return cmmFileService.saveFiles(fileMstDTO, mfList); return cmmFileService.saveFiles(fileMstDTO, mfList);
} }
private CmmFileMstDTO saveFstOverAppendFiles( private CmmFileDTO.FileMst saveFstOverAppendFiles(
final NatlNewspaperRcvXmlDTO.AppendFileInfoDTO apndFileDTO, final NatlNewspaperRcvXmlDTO.AppendFileInfoDTO apndFileDTO,
final CtznSttemntDTO.CtznSttemntDetailDTO stmtDtlDTO, final CtznSttemntDTO.CtznSttemntDetailDTO stmtDtlDTO,
final CmmFileDtlDTO pngFileDtlDto, final CmmFileDTO.FileDtl pngFileDtlDto,
int index) { int index) {
// 첨부 파일 생성 // 첨부 파일 생성
@ -521,7 +520,7 @@ public class EcNatlNewspaperService implements IEcNatlNewspaperService {
} }
*/ */
CmmFileMstDTO fileMstDTO = new CmmFileMstDTO(); CmmFileDTO.FileMst fileMstDTO = new CmmFileDTO.FileMst();
fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode()); fileMstDTO.setJobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode());
fileMstDTO.setFileJobId(stmtDtlDTO.getInterfaceSeqN()+stmtDtlDTO.getCtznSttemntDetailSn()); fileMstDTO.setFileJobId(stmtDtlDTO.getInterfaceSeqN()+stmtDtlDTO.getCtznSttemntDetailSn());
fileMstDTO.setUploadeJobPath(uploadNewsPaperPath); fileMstDTO.setUploadeJobPath(uploadNewsPaperPath);

@ -5,12 +5,12 @@ import java.util.Map;
import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.RowBounds;
import kr.xit.fims.biz.ec.model.EcNatlNewspaperRcvReqDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvReqDTO;
import kr.xit.fims.biz.ec.model.NatlNewspaperRcvDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvDTO;
import kr.xit.framework.core.utils.XitCmmnUtil; import kr.xit.framework.core.utils.XitCmmnUtil;
public interface IEcNatlNewspaperService { public interface IEcNatlNewspaperService {
void saveEsbRvcParse(final EcNatlNewspaperRcvReqDTO dto); void saveEsbRvcParse(final NatlNewspaperRcvReqDTO dto);
List<NatlNewspaperRcvDTO> findNatlNewspaers(final Map<String, Object> paraMap, final RowBounds rowBounds); List<NatlNewspaperRcvDTO> findNatlNewspaers(final Map<String, Object> paraMap, final RowBounds rowBounds);

@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import kr.xit.fims.biz.FimsConst; import kr.xit.fims.biz.FimsConst;
import kr.xit.fims.biz.ec.model.EcNatlNewspaperRcvReqDTO; import kr.xit.fims.biz.ec.model.NatlNewspaperRcvReqDTO;
import kr.xit.fims.biz.ec.service.IEcNatlNewspaperService; import kr.xit.fims.biz.ec.service.IEcNatlNewspaperService;
import kr.xit.framework.core.constants.FrameworkConstants; import kr.xit.framework.core.constants.FrameworkConstants;
import kr.xit.framework.core.model.ResultResponse; import kr.xit.framework.core.model.ResultResponse;
@ -62,7 +62,7 @@ public class EcNatlNewspaperController {
} }
@PostMapping(value = "/saveNatlNewspaers") @PostMapping(value = "/saveNatlNewspaers")
public ModelAndView saveNatlNewspaer(@RequestBody final EcNatlNewspaperRcvReqDTO dto) { public ModelAndView saveNatlNewspaer(@RequestBody final NatlNewspaperRcvReqDTO dto) {
ModelAndView mav = new ModelAndView(FrameworkConstants.JSON_VIEW); ModelAndView mav = new ModelAndView(FrameworkConstants.JSON_VIEW);
service.saveEsbRvcParse(dto); service.saveEsbRvcParse(dto);

@ -1,18 +1,18 @@
package kr.xit.framework.biz.cmm.mapper; // package kr.xit.framework.biz.cmm.mapper;
//
import java.util.Collection; // import java.util.Collection;
import java.util.Optional; // import java.util.Optional;
//
import egovframework.rte.psl.dataaccess.mapper.Mapper; // import egovframework.rte.psl.dataaccess.mapper.Mapper;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; // import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO;
//
@Mapper // @Mapper
public interface ICmmFileDtlMapper { // public interface ICmmFileDtlMapper {
Collection<? extends CmmFileDtlDTO> selectByFileMstId(String fileMstId); // Collection<? extends CmmFileDtlDTO> selectByFileMstId(String fileMstId);
//
int insertCmmFileDtl(CmmFileDtlDTO cmmFileDtl); // int insertCmmFileDtl(CmmFileDtlDTO cmmFileDtl);
//
Optional<CmmFileDtlDTO> selectByFileMstIdAndOrgFileNmIgnoreCase(String fileMstId, String orgFileNm); // Optional<CmmFileDtlDTO> selectByFileMstIdAndOrgFileNmIgnoreCase(String fileMstId, String orgFileNm);
//
int deleteCmmFileDtlById(CmmFileDtlDTO cmmFileDtl); // int deleteCmmFileDtlById(CmmFileDtlDTO cmmFileDtl);
} // }

@ -1,14 +1,10 @@
package kr.xit.framework.biz.cmm.mapper; package kr.xit.framework.biz.cmm.mapper;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.apache.poi.ss.formula.functions.T;
import egovframework.rte.psl.dataaccess.mapper.Mapper; import egovframework.rte.psl.dataaccess.mapper.Mapper;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
@SuppressWarnings("MybatisXMapperMethodInspection") @SuppressWarnings("MybatisXMapperMethodInspection")
@Mapper @Mapper
@ -16,9 +12,9 @@ public interface ICmmFileMapper {
//------------------------------------------------------------- //-------------------------------------------------------------
// CmmFileMaster // CmmFileMaster
//------------------------------------------------------------- //-------------------------------------------------------------
Optional<CmmFileMstDTO> selectCmmFileMastr(final String fileMstId); Optional<CmmFileDTO.FileMst> selectCmmFileMastr(final String fileMstId);
int insertCmmFileMastr(final CmmFileMstDTO cmmFileMst); int insertCmmFileMastr(final CmmFileDTO.FileMst cmmFileMst);
@ -27,11 +23,11 @@ public interface ICmmFileMapper {
//------------------------------------------------------------- //-------------------------------------------------------------
List<CmmFileDtlDTO> selectCmmFileDetails(final String fileMstId); List<CmmFileDTO.FileDtl> selectCmmFileDetails(final String fileMstId);
int insertCmmFileDetail(final CmmFileDtlDTO cmmFileDtl); int insertCmmFileDetail(final CmmFileDTO.FileDtl cmmFileDtl);
Optional<CmmFileDtlDTO> selectCmmFileDetail(final CmmFileDtlDTO dto); Optional<CmmFileDTO.FileDtl> selectCmmFileDetail(final CmmFileDTO.FileDtl dto);
/** /**
* <pre> * <pre>
@ -42,8 +38,8 @@ public interface ICmmFileMapper {
* @return List<CmmFileDtlDTO> * @return List<CmmFileDtlDTO>
* </pre> * </pre>
*/ */
List<CmmFileDtlDTO> selectFilesByJobSeCodeAndJobId(final CmmFileMstDTO dto); List<CmmFileDTO.FileDtl> selectFilesByJobSeCodeAndJobId(final CmmFileDTO.FileMst dto);
int deleteCmmFileDetail(final CmmFileDtlDTO cmmFileDtl); int deleteCmmFileDetail(final CmmFileDTO.FileDtl cmmFileDtl);
} }

@ -1,31 +1,31 @@
package kr.xit.framework.biz.cmm.mapper; // package kr.xit.framework.biz.cmm.mapper;
//
import java.util.Collection; // import java.util.Collection;
import java.util.Optional; // import java.util.Optional;
//
import egovframework.rte.psl.dataaccess.mapper.Mapper; // import egovframework.rte.psl.dataaccess.mapper.Mapper;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; // import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO; // import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
//
@Mapper // @Mapper
public interface ICmmFileMstMapper { // public interface ICmmFileMstMapper {
//------------------------------------------------------------- // //-------------------------------------------------------------
// CmmFileMaster // // CmmFileMaster
//------------------------------------------------------------- // //-------------------------------------------------------------
Optional<CmmFileMstDTO> selectByFileMstId(final String fileMstId); // Optional<CmmFileMstDTO> selectByFileMstId(final String fileMstId);
//
int insertCmmFileMst(CmmFileMstDTO cmmFileMst); // int insertCmmFileMst(CmmFileMstDTO cmmFileMst);
//
//
//
//------------------------------------------------------------- // //-------------------------------------------------------------
// CmmFileDtl // // CmmFileDtl
//------------------------------------------------------------- // //-------------------------------------------------------------
//Collection<? extends CmmFileDtlDTO> selectByFileMstId(String fileMstId); // //Collection<? extends CmmFileDtlDTO> selectByFileMstId(String fileMstId);
//
int insertCmmFileDtl(CmmFileDtlDTO cmmFileDtl); // int insertCmmFileDtl(CmmFileDtlDTO cmmFileDtl);
//
Optional<CmmFileDtlDTO> selectByFileMstIdAndOrgFileNmIgnoreCase(String fileMstId, String orgFileNm); // Optional<CmmFileDtlDTO> selectByFileMstIdAndOrgFileNmIgnoreCase(String fileMstId, String orgFileNm);
//
int deleteCmmFileDtlById(CmmFileDtlDTO cmmFileDtl); // int deleteCmmFileDtlById(CmmFileDtlDTO cmmFileDtl);
} // }

@ -1,6 +1,8 @@
package kr.xit.framework.biz.cmm.model; package kr.xit.framework.biz.cmm.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -12,37 +14,131 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@Getter public class CmmFileDTO {
@Setter
@NoArgsConstructor @Setter
@AllArgsConstructor // Builder 사용시 필요 @Getter
@EqualsAndHashCode @NoArgsConstructor
@Builder @AllArgsConstructor
@ToString @EqualsAndHashCode
public class CmmFileDTO implements Serializable { @Builder
private static final long SerialVersionUID = 1L; @ToString
public static class FileMst implements Serializable {
/** private static final long serialVersionUID = 5741919760452445573L;
* ID(length = 40) - UUID
*/ /**
private String fileMastrId; * ID(length = 40) - UUID
*/
/** private String fileMastrId;
* (ex. Board)
*/ /**
private String jobSeCode; * (ex. Board)
*/
/** private String jobSeCode;
* ID(example = "BOARD001")
* DB pk - + ID /**
*/ * ID(example = "BOARD001")
private String fileJobId; * DB pk - + ID
*/
private String register; private String fileJobId;
/** /**
* * Path : ex) board / natl-newspaper
*/ * properties
private MultipartFile[] files; */
//private List<MultipartFile> files; private String uploadeJobPath;
/**
*
* Y: , M: , D:
* uploadeJobPath
*/
private String fileDirPath;
private String register;
private List<CmmFileDTO.FileDtl> cmmFileDtls = new ArrayList<>();
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder
@ToString
public static class FileDtl implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID(length = 40) - UUID
*/
private String fileMastrId;
/**
* ID(length = 40) - UUID
*/
private String fileId;
/**
*
*/
private String orginlFileNm;
/**
*
*/
private String fileCntntsTy;
/**
*
*/
private String fileExtsn;
/**
*
*/
private Long fileCpcty;
/**
*
*/
private String fileCours;
private String register;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder
@ToString
public static class Request implements Serializable {
private static final long SerialVersionUID = 1L;
/**
* ID(length = 40) - UUID
*/
private String fileMastrId;
/**
* (ex. Board)
*/
private String jobSeCode;
/**
* ID(example = "BOARD001")
* DB pk - + ID
*/
private String fileJobId;
private String register;
/**
*
*/
private MultipartFile[] files;
//private List<MultipartFile> files;
}
} }

@ -1,59 +0,0 @@
package kr.xit.framework.biz.cmm.model;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder
@ToString
public class CmmFileDtlDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID(length = 40) - UUID
*/
private String fileMastrId;
/**
* ID(length = 40) - UUID
*/
private String fileId;
/**
*
*/
private String orginlFileNm;
/**
*
*/
private String fileCntntsTy;
/**
*
*/
private String fileExtsn;
/**
*
*/
private Long fileCpcty;
/**
*
*/
private String fileCours;
private String register;
}

@ -1,59 +0,0 @@
package kr.xit.framework.biz.cmm.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Builder
@ToString
public class CmmFileMstDTO implements Serializable {
private static final long serialVersionUID = 5741919760452445573L;
/**
* ID(length = 40) - UUID
*/
private String fileMastrId;
/**
* (ex. Board)
*/
private String jobSeCode;
/**
* ID(example = "BOARD001")
* DB pk - + ID
*/
private String fileJobId;
private String register;
/**
* Path : ex) board / natl-newspaper
* properties
*/
private String uploadeJobPath;
/**
*
* Y: , M: , D:
* uploadeJobPath
*/
private String fileDirPath;
private List<CmmFileDtlDTO> cmmFileDtls = new ArrayList<>();
}

@ -15,8 +15,7 @@ import org.springframework.web.multipart.MultipartFile;
import kr.xit.fims.biz.FimsConst; import kr.xit.fims.biz.FimsConst;
import kr.xit.framework.biz.cmm.mapper.ICmmFileMapper; import kr.xit.framework.biz.cmm.mapper.ICmmFileMapper;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
import kr.xit.framework.support.exception.BizRuntimeException; import kr.xit.framework.support.exception.BizRuntimeException;
import kr.xit.framework.support.util.Checks; import kr.xit.framework.support.util.Checks;
import kr.xit.framework.support.util.CommUtils; import kr.xit.framework.support.util.CommUtils;
@ -46,10 +45,10 @@ public class CmmFileService implements ICmmFileService {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public CmmFileMstDTO findFiles(final String fileMstId) { public CmmFileDTO.FileMst findFiles(final String fileMstId) {
Assert.notNull(fileMstId, "대상 파일[fileMstId]을 선택해 주세요."); Assert.notNull(fileMstId, "대상 파일[fileMstId]을 선택해 주세요.");
CmmFileMstDTO cmmFileMst = mapper.selectCmmFileMastr(fileMstId).orElse(null); CmmFileDTO.FileMst cmmFileMst = mapper.selectCmmFileMastr(fileMstId).orElse(null);
// CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds(); // CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds();
// cmmFileDtlIds.setFileMstId(cmmFileMst.getFileMstId()); // cmmFileDtlIds.setFileMstId(cmmFileMst.getFileMstId());
@ -59,7 +58,7 @@ public class CmmFileService implements ICmmFileService {
@Override @Override
@Transactional @Transactional
public CmmFileMstDTO saveFiles(final CmmFileMstDTO fileMstDTO, final List<MultipartFile> files) { public CmmFileDTO.FileMst saveFiles(final CmmFileDTO.FileMst fileMstDTO, final List<MultipartFile> files) {
Assert.notNull(fileMstDTO, "파일 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO, "파일 정보가 존재하지 않습니다.");
Assert.notNull(fileMstDTO.getJobSeCode(), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO.getJobSeCode(), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다.");
Assert.notNull(fileMstDTO.getFileJobId(), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO.getFileJobId(), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다.");
@ -91,13 +90,13 @@ public class CmmFileService implements ICmmFileService {
File file = new File(fileUploadPath); File file = new File(fileUploadPath);
if(!file.exists()) file.mkdirs(); if(!file.exists()) file.mkdirs();
List<CmmFileDtlDTO> cmmFileDtls = new ArrayList<>(); List<CmmFileDTO.FileDtl> cmmFileDtls = new ArrayList<>();
for(MultipartFile mf : files){ for(MultipartFile mf : files){
if(!mf.isEmpty()) { if(!mf.isEmpty()) {
String orgFileName = ""; String orgFileName = "";
try { try {
orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename())); orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename()));
CmmFileDtlDTO fileDtlDTO = CmmFileDtlDTO.builder() CmmFileDTO.FileDtl fileDtlDTO = CmmFileDTO.FileDtl.builder()
.fileMastrId(fileMstDTO.getFileMastrId()) .fileMastrId(fileMstDTO.getFileMastrId())
.fileId(CommUtils.getStringFromUUID()) .fileId(CommUtils.getStringFromUUID())
.fileCours(makePath) .fileCours(makePath)
@ -151,7 +150,7 @@ public class CmmFileService implements ICmmFileService {
@Override @Override
@Transactional @Transactional
public CmmFileMstDTO saveCtznSttemntFiles(final CmmFileMstDTO fileMstDTO, final List<MultipartFile> files, final CmmFileDtlDTO pngFileDtlDto) { public CmmFileDTO.FileMst saveCtznSttemntFiles(final CmmFileDTO.FileMst fileMstDTO, final List<MultipartFile> files, final CmmFileDTO.FileDtl pngFileDtlDto) {
Assert.notNull(fileMstDTO, "파일 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO, "파일 정보가 존재하지 않습니다.");
Assert.notNull(fileMstDTO.getJobSeCode(), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO.getJobSeCode(), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다.");
Assert.notNull(fileMstDTO.getFileJobId(), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); Assert.notNull(fileMstDTO.getFileJobId(), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다.");
@ -183,13 +182,13 @@ public class CmmFileService implements ICmmFileService {
File file = new File(fileUploadPath); File file = new File(fileUploadPath);
if(!file.exists()) file.mkdirs(); if(!file.exists()) file.mkdirs();
List<CmmFileDtlDTO> cmmFileDtls = new ArrayList<>(); List<CmmFileDTO.FileDtl> cmmFileDtls = new ArrayList<>();
for(MultipartFile mf : files){ for(MultipartFile mf : files){
if(!mf.isEmpty()) { if(!mf.isEmpty()) {
String orgFileName = ""; String orgFileName = "";
try { try {
orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename())); orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename()));
CmmFileDtlDTO fileDtlDTO = CmmFileDtlDTO.builder() CmmFileDTO.FileDtl fileDtlDTO = CmmFileDTO.FileDtl.builder()
.fileMastrId(fileMstDTO.getFileMastrId()) .fileMastrId(fileMstDTO.getFileMastrId())
.fileId(CommUtils.getStringFromUUID()) .fileId(CommUtils.getStringFromUUID())
.fileCours(makePath) .fileCours(makePath)
@ -250,7 +249,7 @@ public class CmmFileService implements ICmmFileService {
} }
@Transactional @Transactional
public void removeExistsUploadFile(final CmmFileDtlDTO dtlDto){ public void removeExistsUploadFile(final CmmFileDTO.FileDtl dtlDto){
mapper.selectCmmFileDetail(dtlDto) mapper.selectCmmFileDetail(dtlDto)
.ifPresent((dto)->{ .ifPresent((dto)->{
mapper.deleteCmmFileDetail(dto); mapper.deleteCmmFileDetail(dto);

@ -4,8 +4,7 @@ import java.util.List;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import kr.xit.framework.biz.cmm.model.CmmFileDtlDTO; import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
import kr.xit.framework.core.utils.XitCmmnUtil; import kr.xit.framework.core.utils.XitCmmnUtil;
/** /**
@ -13,13 +12,13 @@ import kr.xit.framework.core.utils.XitCmmnUtil;
* @since 2021-07-16 * @since 2021-07-16
*/ */
public interface ICmmFileService { public interface ICmmFileService {
CmmFileMstDTO findFiles(final String fileMstId); CmmFileDTO.FileMst findFiles(final String fileMstId);
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
CmmFileMstDTO saveFiles(final CmmFileMstDTO fileMstDTO, final List<MultipartFile> files); CmmFileDTO.FileMst saveFiles(final CmmFileDTO.FileMst fileMstDTO, final List<MultipartFile> files);
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
CmmFileMstDTO saveCtznSttemntFiles(final CmmFileMstDTO fileMstDTO, final List<MultipartFile> files, final CmmFileDtlDTO pngFileDtlDto); CmmFileDTO.FileMst saveCtznSttemntFiles(final CmmFileDTO.FileMst fileMstDTO, final List<MultipartFile> files, final CmmFileDTO.FileDtl pngFileDtlDto);
default String getUserUniqId(){ default String getUserUniqId(){

@ -1,7 +1,20 @@
package kr.xit.framework.biz.cmm.web; package kr.xit.framework.biz.cmm.web;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -12,11 +25,12 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import kr.xit.framework.biz.cmm.model.CmmFileDTO; import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.model.CmmFileMstDTO;
import kr.xit.framework.biz.cmm.service.ICmmFileService; import kr.xit.framework.biz.cmm.service.ICmmFileService;
import kr.xit.framework.core.constants.ErrorCode;
import kr.xit.framework.core.constants.FrameworkConstants; import kr.xit.framework.core.constants.FrameworkConstants;
import kr.xit.framework.core.model.ResultResponse; import kr.xit.framework.core.model.ResultResponse;
import kr.xit.framework.support.exception.BizRuntimeException; import kr.xit.framework.support.exception.BizRuntimeException;
import kr.xit.framework.support.exception.CustomBaseException;
import kr.xit.framework.support.util.AjaxMessageMapRenderer; import kr.xit.framework.support.util.AjaxMessageMapRenderer;
import kr.xit.framework.support.util.Checks; import kr.xit.framework.support.util.Checks;
import kr.xit.framework.support.util.constants.MessageKey; import kr.xit.framework.support.util.constants.MessageKey;
@ -26,6 +40,8 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/framework/biz/cmm/file") @RequestMapping("/framework/biz/cmm/file")
public class CmmFileMgtController { public class CmmFileMgtController {
@Value("#{prop['file.upload.root']}")
private String uploadRoot;
private final ICmmFileService cmmFileService; private final ICmmFileService cmmFileService;
@GetMapping("/{fileMstId}") @GetMapping("/{fileMstId}")
@ -35,13 +51,13 @@ public class CmmFileMgtController {
} }
@PostMapping(value = "/upload", consumes = "multipart/form-data") @PostMapping(value = "/upload", consumes = "multipart/form-data")
public ModelAndView saveFiles(final CmmFileDTO cmmFileDto) { public ModelAndView saveFiles(final CmmFileDTO.Request cmmFileDto) {
if(Checks.isEmpty(cmmFileDto)) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileDto)) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 정보가 존재하지 않습니다.");
if(Checks.isEmpty(cmmFileDto.getJobSeCode())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileDto.getJobSeCode())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다.");
if(Checks.isEmpty(cmmFileDto.getFileJobId())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileDto.getFileJobId())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다.");
ModelAndView mav = new ModelAndView(FrameworkConstants.JSON_VIEW); ModelAndView mav = new ModelAndView(FrameworkConstants.JSON_VIEW);
CmmFileMstDTO cmmFileMst = CmmFileMstDTO.builder() CmmFileDTO.FileMst cmmFileMst = CmmFileDTO.FileMst.builder()
.fileMastrId(cmmFileDto.getFileMastrId()) .fileMastrId(cmmFileDto.getFileMastrId())
.jobSeCode(cmmFileDto.getJobSeCode()) .jobSeCode(cmmFileDto.getJobSeCode())
.fileJobId(cmmFileDto.getFileJobId()) .fileJobId(cmmFileDto.getFileJobId())
@ -52,7 +68,7 @@ public class CmmFileMgtController {
} }
@PostMapping(value = "/upload2", consumes = "multipart/form-data") @PostMapping(value = "/upload2", consumes = "multipart/form-data")
public ModelAndView saveFiles2(final CmmFileMstDTO cmmFileMst, @RequestParam("files") final MultipartFile[] files) { public ModelAndView saveFiles2(final CmmFileDTO.FileMst cmmFileMst, @RequestParam("files") final MultipartFile[] files) {
if(Checks.isEmpty(cmmFileMst)) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileMst)) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 정보가 존재하지 않습니다.");
if(Checks.isEmpty(cmmFileMst.getJobSeCode())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileMst.getJobSeCode())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다.");
if(Checks.isEmpty(cmmFileMst.getFileJobId())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); if(Checks.isEmpty(cmmFileMst.getFileJobId())) throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다.");
@ -63,4 +79,70 @@ public class CmmFileMgtController {
AjaxMessageMapRenderer.success(mav, MessageKey.CMM_INSERT_SUCCESS); AjaxMessageMapRenderer.success(mav, MessageKey.CMM_INSERT_SUCCESS);
return mav; return mav;
} }
@GetMapping("/download/{inCode}")
public void download(@PathVariable Long inCode, HttpServletResponse response) {
String absFile = "";
download(absFile, "", response);
}
@GetMapping("/download")
public void download(final String scDatagb, final Long scCode, String methodName, HttpServletResponse response) {
String absFile = "";
download(absFile, "", response);
}
private void download(String absFile, String fileName, HttpServletResponse response) {
Path path = Paths.get(absFile);
String contentType = null;
long fileSize = 0L;
try {
contentType = Files.probeContentType(path);
fileSize = Files.size(path);
} catch (IOException e) {
//throw new CustomBaseException(ErrorCode.FILE_NOT_FOUND);
contentType = "application/octet-stream";
//fileName = "FileNotFound";
}
File file = new File(absFile);
byte[] fileByte = new byte[0];
try {
fileByte = FileUtils.readFileToByteArray(file);
} catch (IOException e) {
fileByte = new byte[0];
//throw new CustomBaseException(ErrorCode.FILE_NOT_FOUND);
}
response.setContentType(contentType);
response.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
try {
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileName,
String.valueOf(StandardCharsets.UTF_8)) + "\";");
} catch (UnsupportedEncodingException e) {
throw BizRuntimeException.create(MessageKey.CUSTOM_MSG, e.getMessage());
}
//response.setHeader(HttpHeaders.CONTENT_ENCODING, "binary");
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize));
try {
response.getOutputStream().write(fileByte);
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (IOException e) {
throw new CustomBaseException(ErrorCode.FILE_NOT_FOUND);
}
}
} }

@ -6,7 +6,7 @@
<!-- ************************************************************************************************************* <!-- *************************************************************************************************************
* tb_cmm_file_mastr : 파일 공통 마스터 * tb_cmm_file_mastr : 파일 공통 마스터
************************************************************************************************************** --> ************************************************************************************************************** -->
<select id="selectCmmFileMastr" resultType="kr.xit.framework.biz.cmm.model.CmmFileMstDTO"> <select id="selectCmmFileMastr" resultType="kr.xit.framework.biz.cmm.model.CmmFileDTO$FileMst">
/* cmm-file-mysql-mapper|insertCmmFileMastr- 파일 마스터 정보 등록|julim */ /* cmm-file-mysql-mapper|insertCmmFileMastr- 파일 마스터 정보 등록|julim */
SELECT file_mastr_id SELECT file_mastr_id
, job_se_code , job_se_code
@ -62,14 +62,14 @@
FROM tb_cmm_file_detail cfd FROM tb_cmm_file_detail cfd
</sql> </sql>
<select id="select" resultType="kr.xit.framework.biz.cmm.model.CmmFileMstDTO"> <select id="select" resultType="kr.xit.framework.biz.cmm.model.CmmFileDTO$FileDtl">
/* cmm-file-mysql-mapper|selectCmmFileDetails- 파일 상세 목록 조회|julim */ /* cmm-file-mysql-mapper|selectCmmFileDetails- 파일 상세 목록 조회|julim */
<include refid="sqlCmmFileDetail"/> <include refid="sqlCmmFileDetail"/>
WHERE cfd.file_mastr_id = #{fileMastrId} WHERE cfd.file_mastr_id = #{fileMastrId}
</select> </select>
<!--suppress SqlResolve --> <!--suppress SqlResolve -->
<select id="selectFilesByJobSeCodeAndJobId" resultType="kr.xit.framework.biz.cmm.model.CmmFileMstDTO"> <select id="selectFilesByJobSeCodeAndJobId" resultType="kr.xit.framework.biz.cmm.model.CmmFileDTO$FileMst">
/* cmm-file-mysql-mapper|selectFilesByJobSeCodeAndJobId- 파일 상세 목록 조회:업무코드 and 업무아이디로|julim */ /* cmm-file-mysql-mapper|selectFilesByJobSeCodeAndJobId- 파일 상세 목록 조회:업무코드 and 업무아이디로|julim */
<include refid="sqlCmmFileDetail"/> <include refid="sqlCmmFileDetail"/>
WHERE EXISTS ( WHERE EXISTS (
@ -82,7 +82,7 @@
</select> </select>
<select id="selectCmmFileDetail" resultType="kr.xit.framework.biz.cmm.model.CmmFileMstDTO"> <select id="selectCmmFileDetail" resultType="kr.xit.framework.biz.cmm.model.CmmFileDTO$FileMst">
/* cmm-file-mysql-mapper|selectCmmFileDetail- 파일 상세 조회|julim */ /* cmm-file-mysql-mapper|selectCmmFileDetail- 파일 상세 조회|julim */
<include refid="sqlCmmFileDetail"/> <include refid="sqlCmmFileDetail"/>
WHERE cfd.file_mastr_id = #{fileMastrId} WHERE cfd.file_mastr_id = #{fileMastrId}

Loading…
Cancel
Save