OWL ITS + 탐지시스템(인터넷 진흥원)
이민희
2021-11-24 237973a4f5bf62471fdbe2019cc725ca51bcc4b6
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -44,7 +44,6 @@
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.Condition;
@Service
public class IssueServiceImpl extends AbstractServiceImpl<Issue, Long, JpaRepository<Issue, Long>> implements IssueService {
@@ -53,6 +52,9 @@
    @Autowired
    private IssueRepository issueRepository;
    @Autowired
    private IssueTableConfigService issueTableConfigService;
    @Autowired
    private ProjectService projectService;
@@ -91,6 +93,15 @@
    private IssueCustomFieldValueService issueCustomFieldValueService;
    @Autowired
    private IssueCompanyService issueCompanyService;
    @Autowired
    private IssueIspService issueIspService;
    @Autowired
    private IssueHostingService issueHostingService;
    @Autowired
    private IssueUserService issueUserService;
    @Autowired
@@ -104,6 +115,9 @@
    @Autowired
    private UserService userService;
    @Autowired
    private DepartmentService departmentService;
    @Autowired
    private IssueCommentService issueCommentService;
@@ -186,6 +200,8 @@
        this.verifyTitle(issueForm.getTitle());
        //  날짜 유효성 체크
        this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
        //  담당 부서 유효성 체크
        this.verifyIssueDepartment(project, issueForm);
        //  이슈 상태 유형이 '대기' 인 이슈 상태 가져오기
        IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
@@ -197,22 +213,6 @@
        issue.setPriority(priority);
        issue.setSeverity(severity);
        //  업체 필드 유효성 체크
        if(issueForm.getCompanyId() != null){
            CompanyField companyField = this.companyFieldService.getCompany(issueForm.getCompanyId());
            issue.setCompanyField(companyField);
        }
        //  ISP 필드 유효성 체크
        if(issueForm.getIspId() != null) {
            IspField ispField = this.ispFieldService.getIsp(issueForm.getIspId());
            issue.setIspFieldId(ispField);
        }
        //  HOSTING 필드 유효성 체크
        if (issueForm.getHostingId() != null){
            HostingField hostingField = this.hostingFieldService.getHosting(issueForm.getHostingId());
            issue.setHostingFieldId(hostingField);
        }
        issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project));    //  각 프로젝트의 고유 이슈 번호 생성
        this.issueRepository.saveAndFlush(issue);
@@ -222,6 +222,12 @@
        //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
@@ -346,6 +352,9 @@
        issueMap.put("customFields", customFields);
        issueMap.put("description", issue.getDescription());
        //업체,ISP,HOSTING 추가
        StringBuilder attachedFileBuilder = new StringBuilder();
        List<AttachedFile> attachedFiles = this.attachedFileService.findByIssueId(issue.getId());
@@ -427,13 +436,13 @@
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        //  튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9
        StopWatch serviceStart = new StopWatch();
        serviceStart.start();
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
        Long totalCount = this.issueMapper.count(issueCondition);
        //  튜닝 전 - 1.1, 1.1, 1.3, 1.2
        serviceStart.stop();
        log.debug("serviceENd1 : " + serviceStart.getTime());
        /*serviceStart.stop();
        log.debug("serviceENd1 : " + serviceStart.getTime());*/
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
@@ -454,7 +463,7 @@
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findChartIssue(Map<String, Object> resJsonData,
                                   IssueCondition issueCondition, Pageable pageable) {
                                        IssueCondition issueCondition, Pageable pageable) {
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition,Lists.newArrayList("01", "02", "03"), pageable)) {
@@ -566,7 +575,7 @@
        this.setIssueCustomFieldValue(issueVos, issueCondition);
    }
        //  검색 조건을 만든다
    //  검색 조건을 만든다
    private boolean makeIssueSearchCondition(IssueCondition condition, List<String> projectStatues, Pageable pageable) {
        if (pageable != null) {
            condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
@@ -799,6 +808,7 @@
                    this.setAttachedFiles(issue, issueVo);  //  첨부 파일 정보 셋팅
                    this.setIssueCustomFields(issue, issueVo);  //  사용자 정의 필드 값 정보 셋팅
                    this.setRelationIssue(issue, issueVo);        //연관 일감 셋팅
                    this.setDownIssues(issue, issueVo); //하위 이슈 세팅
                    break;
@@ -812,6 +822,24 @@
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_DETAIL));
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo);
    }
    // 하위 이슈 정보를 셋팅한다
    private void setDownIssues(Issue issue, IssueVo issueVo) {
        List<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId());
        List<IssueVo> downIssueVos = ConvertUtil.convertObjectsToClasses(downIssues, IssueVo.class);
        for(IssueVo downIssueVo : downIssueVos){
            for(Issue downIssue : downIssues){
                downIssueVo.setTitle(downIssue.getTitle());
                downIssueVo.setIssueTypeVo(ConvertUtil.copyProperties(downIssue.getIssueType(), IssueTypeVo.class));
                downIssueVo.setPriorityVo(ConvertUtil.copyProperties(downIssue.getPriority(), PriorityVo.class));
                downIssueVo.setSeverityVo(ConvertUtil.copyProperties(downIssue.getSeverity(), SeverityVo.class));
                this.setRegister(downIssue, downIssueVo); // 등록자
                this.setIssueDepartment(downIssue, downIssueVo);  //  담당부서 정보 셋팅
            }
            issueVo.setIssueDownVos(downIssueVo);
        }
    }
    //  이슈 상세 정보를 셋팅한다.
@@ -833,6 +861,19 @@
        this.setIssueComments(issue, issueVo);  //  댓글 정보 셋팅
        this.setIssueHistory(issue, issueVo);   //  이슈 기록 정보 셋팅
        this.setRelationIssue(issue, issueVo);        //연관 일감 셋팅
        this.setDownIssues(issue, issueVo);
        this.setIssueCompanyField(issue, issueVo);  //업체 정보 세팅
        this.setIssueIspField(issue, issueVo);  //ISP 정보 세팅
        this.setIssueHostingField(issue, issueVo);  //HOSTING 정보 세팅
        this.setParentIssue(issue,issueVo); //상위 이슈 정보 세팅
    }
    //  상위일감 정보 추가
    private void setParentIssue(Issue issue, IssueVo issueVo) {
        if(issue.getParentIssue() != null){
            issueVo.setParentIssueVo(ConvertUtil.copyProperties(issue.getParentIssue(), IssueVo.class));
        }
    }
    //  등록자 정보 추가
@@ -871,6 +912,11 @@
                issueRelationVo.setIssueRelation(relIssueVo);
                issueRelationVo.setTitle(relationIssue.getTitle());
                issueRelationVo.setIssueTypeVo(ConvertUtil.copyProperties(relationIssue.getIssueType(), IssueTypeVo.class));
                issueRelationVo.setPriorityVo(ConvertUtil.copyProperties(relationIssue.getPriority(), PriorityVo.class));
                issueRelationVo.setSeverityVo(ConvertUtil.copyProperties(relationIssue.getSeverity(), SeverityVo.class));
                this.setRegister(relationIssue, relIssueVo); // 등록자
                this.setIssueDepartment(relationIssue, relIssueVo);  //  담당부서 정보 셋팅
                issueVo.addIssueRelationVo(issueRelationVo);
            }
        } else {
@@ -920,7 +966,7 @@
        for (IssueDepartment issueDepartment : issue.getIssueDepartments()) {
            DepartmentVo departmentVo = ConvertUtil.copyProperties(issueDepartment.getDepartment(), DepartmentVo.class);
            departmentVo.setByName(departmentVo.getByName());
            departmentVo.setByName(departmentVo.getDepartmentName());
            departmentVos.add(departmentVo);
        }
        issueVo.setDepartmentVos(departmentVos);
@@ -1039,9 +1085,15 @@
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, detectIssueChange.toString());
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_MODIFY));
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        return issue;
    }
@@ -1116,7 +1168,7 @@
                //  데이터 보정 작업 - 프로젝트에서 제외된 사용자는 담당자에서 제외 될 수 있도록 처리
                if (!includeProject) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_USER));
                            this.messageAccessor.getMessage(MsgConstants.PROJECT_NOT_INCLUDE_DEPARTMENT));
                }
            }
            //  참여 확인된 부서로 담당부서 변경
@@ -1181,11 +1233,11 @@
        boolean hasPermission = false;
        //  업무 공간 관리자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null, null);
        //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null, null);
        //   이슈 등록자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null, null);
        //  이슈 담당자일 경우 수정 권한을 갖는다. => 담당부서로 수정 - 체크
        //hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ASSIGNEE, issueVo, issueUserVos);
        //  담당자가 없으면 모든 사용자가 수정 권한을 갖는다.
@@ -1194,7 +1246,7 @@
    }
    //  이슈 수정 권한을 확인한다.
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos) {
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos, List<DepartmentVo> issueDepartmentVos) {
        if (!hasPermission) {
            switch (checkType) {
                case Issue.WORKSPACE_MANAGER:  //  업무 공간 관리자
@@ -1218,15 +1270,28 @@
                        hasPermission = true;
                        break;
                    }
                    //   이슈 담당자 여부 확인 => 담당부서로 수정 - 체크
                    /*for (UserVo issueUserVo : issueUserVos) {
                    //   이슈 담당자 여부 확인
                    for (UserVo issueUserVo : issueUserVos) {
                        if (issueUserVo.getId().equals(this.webAppUtil.getLoginId())) {
                            hasPermission = true;
                            break;
                        }
                    }*/
                    }
                    break;
                case Issue.DEPARTMENT:
                    //  담당부서가 없으면 모든 사용자가 수정 권한을 갖는다.
                    if (issueDepartmentVos.size() < 1) {
                        hasPermission = true;
                        break;
                    }
                    //   이슈 담당부서 여부 확인
                    /*for (DepartmentVo issueDepartmentVo : issueDepartmentVos) {
                        if (issueDepartmentVo.getId().equals()) {
                            hasPermission = true;
                            break;
                        }
                    }*/
                    break;
            }
        }
@@ -1281,7 +1346,7 @@
    }
    //  이슈 담당자 변경
    /*@Override
    @Override
    @Transactional
    public void modifyIssueUser(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
@@ -1314,7 +1379,7 @@
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_USER_CHANGE));
    }*/
    }
    @Override
    @Transactional
@@ -1329,7 +1394,7 @@
        issue.setProject(this.projectService.getProject(issueForm.getProjectId()));
        //  변경 이력 정보 추출
        this.issueHistoryService.detectIssueManager(issue, issueForm, detectIssueChange);
        this.issueHistoryService.detectIssueDepartment(issue, issueForm, detectIssueChange);
        //this.issueUserService.modifyIssueUser(issue, issue.getProject().getWorkspace(), issueForm.getUserIds());
        this.issueDepartmentService.modifyIssueDepartment(issue, issue.getProject().getWorkspace(), issueForm.getDepartmentIds());
@@ -1519,7 +1584,7 @@
        excelInfo.addAttrInfos(new ExportExcelAttrVo("title", this.messageAccessor.message("common.issueTitle"), 40, ExportExcelAttrVo.ALIGN_LEFT)); // 이슈 제목
        excelInfo.addAttrInfos(new ExportExcelAttrVo("description", this.messageAccessor.message("common.content"), 60, ExportExcelAttrVo.ALIGN_LEFT)); // 내용
        excelInfo.addAttrInfos(new ExportExcelAttrVo("issueTypeName", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입
        excelInfo.addAttrInfos(new ExportExcelAttrVo("department", this.messageAccessor.message("common.department"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 담당부서
        excelInfo.addAttrInfos(new ExportExcelAttrVo("departments", this.messageAccessor.message("common.department"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 담당부서
        excelInfo.addAttrInfos(new ExportExcelAttrVo("priorityName", this.messageAccessor.message("common.priority"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위
        excelInfo.addAttrInfos(new ExportExcelAttrVo("severityName", this.messageAccessor.message("common.importance"), 6, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도
        excelInfo.addAttrInfos(new ExportExcelAttrVo("register", this.messageAccessor.message("common.register"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 등록자
@@ -1597,6 +1662,46 @@
        }
    }
    //  업체 정보 추가
    private void setIssueCompanyField(Issue issue, IssueVo issueVo) {
        List<IssueCompanyVo> issueCompanyVos = Lists.newArrayList();
        for(IssueCompany issueCompany : issue.getIssueCompanies()){
            IssueCompanyVo issueCompanyVo = ConvertUtil.copyProperties(issueCompany, IssueCompanyVo.class);
            issueCompanyVo.setId(issueCompany.getId());
            CompanyField companyField = issueCompany.getCompanyField();
            if (companyField != null) {
                issueCompanyVo.setCompanyId(issueCompany.getCompanyField().getId());
            }
            issueCompanyVos.add(issueCompanyVo);
        }
        issueVo.setIssueCompanyVos(issueCompanyVos);
    }
    //  Isp 정보 추가
    private void setIssueIspField(Issue issue, IssueVo issueVo) {
        List<IssueIspVo> issueIspVos = Lists.newArrayList();
        for(IssueIsp issueIsp : issue.getIssueIspFields()){
            IssueIspVo issueIspVo = ConvertUtil.copyProperties(issueIsp, IssueIspVo.class);
            issueIspVo.setId(issueIsp.getId());
            issueIspVos.add(issueIspVo);
        }
        issueVo.setIssueIspVos(issueIspVos);
    }
    // Hosting 정보 추가
    private void setIssueHostingField(Issue issue, IssueVo issueVo) {
        List<IssueHostingVo> issueHostingVos = Lists.newArrayList();
        for(IssueHosting issueHosting : issue.getIssueHostingFields()){
            IssueHostingVo issueHostingVo = ConvertUtil.copyProperties(issueHosting, IssueHostingVo.class);
            issueHostingVo.setId(issueHosting.getId());
            issueHostingVos.add(issueHostingVo);
        }
        issueVo.setIssueHostingVos(issueHostingVos);
    }
    //  연관일감 정보 추가
    private void setRelationIssue(IssueVo issueVo, Long issueId) {
        List<IssueVo> relationIssues = this.issueRelationService.findRelationIssue(issueId);
@@ -1624,6 +1729,7 @@
                result.put("description", description);
                result.put("issueTypeName", issueVo.getIssueTypeName());
                result.put("assignees", CommonUtil.convertUserVosToString(issueVo.getUserVos()));
                result.put("departments", CommonUtil.convertDepartmentVosToString(issueVo.getDepartmentVos()));
                result.put("priorityName", issueVo.getPriorityName());
                result.put("severityName", issueVo.getSeverityName());
@@ -1649,6 +1755,8 @@
                        result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), useValue + ", " + issueCustomFieldValueVo.getUseValue());
                    }
                }
                //업체, ISP, 호스팅 추가
                results.add(result);
            } catch (Exception e) {
@@ -1735,13 +1843,14 @@
            Map<String, Priority> priorityMaps = new HashMap<>();   //  우선 순위 모음
            Map<String, Severity> severityMaps = new HashMap<>();   //  중요도 모음
            Map<String, Object> userMaps = new HashMap<>(); //  사용자 모음
            Map<String, Object> departmentMaps = new HashMap<>(); //  부서 모음
            Map<String, CustomField> customFieldMaps = new HashMap<>();
            Map<String, IssueStatus> issueStatusReadyMaps = new HashMap<>();   //  상태 속성 '대기'인 이슈 상태
            Map<Long, Long> issueNumberMaps = new HashMap<>();  //  이슈 번호 모음
            Map<String, Long> issueTypeCustomFieldMaps = new HashMap<>(); //  이슈 타입 + 사용자 정의 필드 연결 정보
            Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());  //  이슈를 넣으려는 업무 공간
            //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
            this.IssueAttributeMapToList(projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, issueTypeCustomFieldMaps, issueStatusReadyMaps);
            this.IssueAttributeMapToList(projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, departmentMaps, customFieldMaps, issueNumberMaps, issueTypeCustomFieldMaps, issueStatusReadyMaps);
            //  0.237 - 0.230
            List<IssueForm> issueForms = Lists.newArrayList();
@@ -1813,6 +1922,8 @@
            //  이슈 사용자 정의 값 필드 벌크 등록
            this.bulkInsertIssueCustomFieldValue(issueForms, issueTypeCustomFieldMaps);
            //  3.628 - 3.445
            // 업체,ISP,호스팅 추가
            /*serviceStart.stop();
            log.debug("2차 저장 시간 : " + serviceStart.getTime());*/
@@ -1892,6 +2003,7 @@
            Map<String, Long> issueRiskMap = new HashMap<>();
            issueRiskMap.put("issueId", issueForm.getId());
            issueRiskMap.put("changeAssigneeCount", 0L);
            issueRiskMap.put("changeDepartmentCount", 0L);
            issueRiskMap.put("changeIssueStatusCount", 0L);
            issueRiskMap.put("workspaceId", workspace.getId());
            issueRiskMap.put("issueStatusIds", issueForm.getIssueStatusId());
@@ -1932,7 +2044,7 @@
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    private void IssueAttributeMapToList(Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, Object> userMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, Map<String, Long> issueTypeCustomFieldMaps, Map<String, IssueStatus> issueStatusReadyMaps) {
                                         Map<String, Object> userMaps, Map<String, Object> departmentMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, Map<String, Long> issueTypeCustomFieldMaps, Map<String, IssueStatus> issueStatusReadyMaps) {
        //  프로젝트 키로 바로 찾을 수 있게 준비
        List<Project> projects = this.projectService.findByWorkspaceId();
        List<Long> projectIds = Lists.newArrayList();
@@ -1958,6 +2070,14 @@
            }
            userMaps.put(project.getProjectKey(), userMap);
            //  프로젝트에 참여하는 부서 정보
            List<Map<String, Object>> departments = this.departmentService.findProjectDepartment(project);
            List<Long> departmentList = Lists.newArrayList();
            //  부서 정보를 저장
            for (Map<String, Object> department : departments) {
                departmentList.add(MapUtil.getLong(department, "departmentId"));
            }
        }
        //  이슈 유형을 바로 찾을 수 있게 준비
@@ -2512,4 +2632,28 @@
        return tasks;
    }
    @Transactional
    @Override
    public void modifyParentIssue(IssueForm issueDownForm) {
        Issue issue = this.getIssue(issueDownForm.getId()); //하위 이슈
        Issue parentIssue = issue.getParentIssue(); //변경 전 하위이슈의 상위이슈
        Long newParentIssueId = issueDownForm.getParentIssueId(); //변경할 하위이슈의 상위이슈
        StringBuilder sb = new StringBuilder();
        if (newParentIssueId != null) { // 추가 할 경우
           // todo 이전 하위 일감 히스토리기록 필요
            parentIssue = this.getIssue(newParentIssueId); //상위이슈(myIssue)
            issue.setParentIssue(parentIssue); //myIssue를 하위이슈의 상위이슈로 set
            this.issueHistoryService.detectDownIssues(IssueHistoryType.ADD, issue, sb); //issue = 하위이슈
        } else  {
            // 삭제 할 경우
            this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb);
            issue.setParentIssue(null);
        }
        this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString()); //parentIssue = myIssue(기록은 현재 상세페이지에 해야하니까)
        this.issueRepository.saveAndFlush(issue);
    }
}