OWL ITS + 탐지시스템(인터넷 진흥원)
jhjang
2022-01-05 ce82939b2d2ef793e446f464314c6e570c7ebad5
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -82,6 +82,15 @@
    private ApiTokenService apiTokenService;
    @Autowired
    private CompanyFieldService companyFieldService;
    @Autowired
    private IspFieldService ispFieldService;
    @Autowired
    private HostingFieldService hostingFieldService;
    @Autowired
    private CommonConfiguration configuration;
    @Autowired
@@ -202,6 +211,15 @@
        this.issueVersionService.addIssueVersion(issue);
    }
    @Override
    @Transactional
    public void addIssueVersion(Long id, Long userId) {
        Issue issue = this.getIssue(id);
        User user = this.userService.getUser(userId);
        //  이슈 버전 생성
        this.issueVersionService.addIssueVersion(issue, user);
    }
    private IssueForm convertToIssueForm(IssueApiForm issueApiForm, User user) {
        if (issueApiForm.getIssueTypeId() == null) {
@@ -282,6 +300,8 @@
            // 사용자 정의 필드 설정
            issueForm.setIssueCustomFields(issueApiForm.getCustomFieldValues());
            //  같은 도메인 업체 찾기
            this.findCompanyField(issueForm);
            // api 입력값 적용
            ConvertUtil.copyProperties(issueApiForm, issueForm);
@@ -291,6 +311,52 @@
        } else {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR));
        }
    }
    private IssueForm findCompanyField(IssueForm issueForm) {
        if(issueForm.getIssueCustomFields() != null && issueForm.getIssueCustomFields().size() > 0) {
            CompanyFieldCondition condition = new CompanyFieldCondition();
            List<Map<String, Object>> companyFields = this.companyFieldService.find(condition);
            List<Map<String, Object>> issueCompanyFields = Lists.newArrayList();
            List<Map<String, Object>> issueIspFields = Lists.newArrayList();
            List<Map<String, Object>> issueHostingFields = Lists.newArrayList();
            for (Map<String, Object> issueCustomField : issueForm.getIssueCustomFields()) {
                int customFieldId = (Integer) issueCustomField.get("customFieldId");
                Long customId = (long) customFieldId;
                CustomField customField = this.customFieldService.getCustomField(customId);
                if(customField != null && customField.getCustomFieldType().toString().equals("SITE") && customField.getName().equals("도메인")) {
                    String useValue = issueCustomField.get("useValue").toString();
                    if(companyFields != null && companyFields.size() > 0) {
                        for (Map<String, Object> companyField : companyFields) {
                            CompanyFieldVo companyFieldVo = ConvertUtil.convertMapToClass(companyField, CompanyFieldVo.class);
                            if(useValue.equals(companyFieldVo.getUrl())) {
                                companyField.put("companyId", companyField.get("id"));
                                issueCompanyFields.add(companyField);
                                if(companyFieldVo.getIspId() != null) {
                                    Map<String, Object> ispField = this.ispFieldService.find(companyFieldVo.getIspId());
                                    if (ispField != null) {
                                        ispField.put("ispId", ispField.get("id"));
                                        issueIspFields.add(ispField);
                                    }
                                }
                                if(companyFieldVo.getHostingId() != null) {
                                    Map<String, Object> hostingField = this.hostingFieldService.find(companyFieldVo.getHostingId());
                                    if (hostingField != null) {
                                        hostingField.put("hostingId", hostingField.get("id"));
                                        issueHostingFields.add(hostingField);
                                    }
                                }
                            }
                        }
                    }
                }
                issueForm.setIssueCompanyFields(issueCompanyFields);
                issueForm.setIssueIspFields(issueIspFields);
                issueForm.setIssueHostingFields(issueHostingFields);
            }
        }
        return issueForm;
    }
    private User convertToUser(String token) {
@@ -310,12 +376,20 @@
        IssueForm issueForm = this.convertToIssueForm(issueApiForm, user);
        List<Issue> issues = Lists.newArrayList();
        if (issueForm.getParentIssueId() != null) {
        if (issueForm.getParentIssueId() != null    // 기존 추가된 상위 일감이 없거나 설정된 중복 이슈 id가 없을때
                || issueApiForm.getUseIssueCustomFieldIds().size() == 0) {
            issues.add(addIssue(user, issueForm, issueApiForm.getMultipartFiles()));
        } else {
            // 상위 이슈 추가
            // 가상 상위 이슈 추가
            IssueForm parentIssueForm = issueForm.clone();
            // 가상 상위 이슈 추가
            parentIssueForm.setUseIssueCustomFields(issueApiForm.getUseIssueCustomFieldIds());
            //  같은 도메인 업체 찾기
            IssueForm partners = this.findCompanyField(parentIssueForm);
            parentIssueForm.setIssueCompanyFields(partners.getIssueCompanyFields());
            parentIssueForm.setIssueIspFields(partners.getIssueIspFields());
            parentIssueForm.setIssueHostingFields(partners.getIssueHostingFields());
            Issue issue = addIssue(user, parentIssueForm, null);
            issues.add(issue);
            // 하위 이슈 추가
@@ -348,7 +422,9 @@
            IssueCustomFieldValueFormComparator comp = new IssueCustomFieldValueFormComparator();
            Collections.sort(issueCustomFieldValueForms, comp);
            List<String> userValues = Lists.newArrayList();
            for (IssueCustomFieldValueForm issueCustomFieldValueForm : issueCustomFieldValueForms) {
                userValues.add(issueCustomFieldValueForm.getUseValue());
                for(CustomFieldApiOverlap customFieldApiOverlap : customFieldApiOverlaps) {
                    if (customFieldApiOverlap.getCustomField().getId().equals(issueCustomFieldValueForm.getCustomFieldId())) {
                        if (useIdx > 0) {
@@ -362,6 +438,7 @@
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            issueCustomFieldValueCondition.setUseValues(userValues);
            issueCustomFieldValueCondition.setIssueTypeId(issueApiform.getIssueTypeId());
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            if (results != null && results.size() > 0) {
@@ -427,12 +504,11 @@
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -465,6 +541,91 @@
    public Issue addRelIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return addRelIssue(user, issueForm, multipartFiles);
    }
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return addDownIssue(user, issueForm, multipartFiles);
    }
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId());
        //  프로젝트 유효성 체크
        Project project = this.projectService.getProject(issueForm.getProjectId());
        //  이슈 유형 유효성 체크
        IssueType issueType = this.issueTypeService.getIssueType(issueForm.getIssueTypeId());
        //  우선순위 유효성 체크
        Priority priority = this.priorityService.getPriority(issueForm.getPriorityId());
        //  중요도 유효성 체크
        Severity severity = this.severityService.getSeverity(issueForm.getSeverityId());
        //  제목 유효성 체크
        this.verifyTitle(issueForm.getTitle());
        //  날짜 유효성 체크
        this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
        //  담당 부서 유효성 체크
        //this.verifyIssueDepartment(project, issueForm);
        //  이슈 상태 유형이 '대기' 인 이슈 상태 가져오기
        IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
        Issue issue = ConvertUtil.copyProperties(issueForm, Issue.class);
        issue.setProject(project);
        issue.setIssueStatus(issueStatus);
        issue.setIssueType(issueType);
        issue.setPriority(priority);
        issue.setSeverity(severity);
        if (issueForm.getParentIssueId() != null){
            Issue parentIssue = this.getIssue(issueForm.getParentIssueId());
            issue.setParentIssue(parentIssue);
        }
        issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project));    //  각 프로젝트의 고유 이슈 번호 생성
        issue = this.issueRepository.saveAndFlush(issue);
        issue.setReverseIndex(issue.getId() * -1);  //  쿼리 속도 개선을 위해 리버스 인덱스 생성
        //  담당자 지정
        //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
        this.attachedFileService.addAttachedFile(convertFileMaps, issue, user.getAccount());
        //  텍스트 에디터에 첨부한 파일을 이슈와 연결
        this.checkNotHaveIssueIdAttachedFile(issue, issueForm);
        //  사용자 정의 필드 저장
        this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields());
        //  이슈 이력 생성
        this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.ADD, null);
        //  이슈 위험 관리 생성
        this.issueRiskService.addIssueRisk(issue, project.getWorkspace());
        //  영속성 컨텍스트 비우기
        this.clear();
        //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
        this.reservationIssueEmail(issue, EmailType.ISSUE_ADD);
        //  사용자 시스템 기능 사용 정보 수집
        UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class);
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_ADD));
        return issue;
    }
    //  연관이슈를 생성한다.
@@ -513,11 +674,11 @@
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -565,7 +726,7 @@
    }
    //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
    private void makeIssueMapToIssue(Issue issue, Map<String, Object> issueMap) {
    public void makeIssueMapToIssue(Issue issue, Map<String, Object> issueMap) {
        issueMap.put("title", issue.getTitle());
        issueMap.put("issueNumber", issue.getIssueNumber());
        issueMap.put("issueTypeName", issue.getIssueType().getName());
@@ -741,7 +902,6 @@
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findIssue(Map<String, Object> resJsonData, IssueCondition issueCondition, Pageable pageable) {
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
@@ -775,14 +935,14 @@
        Long totalCount = 0L;
        UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
        if (this.userWorkspaceService.checkWorkspaceManager(user)
                || MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) {
            this.SetAllDepartmentId(issueCondition);
        } else{
            this.SetMyDepartmentId(issueCondition);
            /*results = this.issueMapper.findByDepartment(issueCondition);
            totalCount = this.issueMapper.countByDepartment(issueCondition);*/
        }
//        if (!this.userWorkspaceService.checkWorkspaceManager(user)
//                && !MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) { //최고관리자 & 프로젝트,이슈 관리자 일 경우 모든 이슈 보기
//            this.SetMyDepartmentId(issueCondition);
            //this.SetAllDepartmentId(issueCondition);
//        } /*else{
//            results = this.issueMapper.findByDepartment(issueCondition);
//            totalCount = this.issueMapper.countByDepartment(issueCondition);
//        }*/
        results = this.issueMapper.find(issueCondition);
        totalCount = this.issueMapper.count(issueCondition);
@@ -1089,6 +1249,9 @@
        condition.setLoginUserId(this.webAppUtil.getLoginId());
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        User user = this.webAppUtil.getLoginUserObject();
        UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
        //  프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다.
        if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) {
            return false;
@@ -1096,9 +1259,17 @@
        //  프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다.
        if (condition.getProjectIds().size() < 1) {
            List<Map<String, Object>> projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectStatues, condition.getProjectType());
            List<Long> projectIds = Lists.newArrayList();
            List<Map<String, Object>> projects = Lists.newArrayList();
            if (this.userWorkspaceService.checkWorkspaceManager(user) || MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_PROJECT)){
                return true;
            }/*else if (MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)){
                projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectStatues, condition.getProjectType());
            }*/
            else {
                projects = this.projectService.findByWorkspaceIdAndIncludeProject(projectStatues, condition.getProjectType());
            }
            List<Long> projectIds = Lists.newArrayList();
            for (Map<String, Object> result : projects) {
                Long projectId = MapUtil.getLong(result, "id");
@@ -1138,7 +1309,8 @@
            List<Map<String, Object>> projects = null;
            UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
            if (this.userWorkspaceService.checkWorkspaceManager(user)
                    || MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) {
                    || (MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_PROJECT) &&
                    MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE))) {
                projects = this.projectMapper.findByWorkspaceManagerAll(projectCondition);
            } else  {
                projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
@@ -1371,6 +1543,10 @@
                this.setRegister(downIssue, downIssueVo); // 등록자
                this.setIssueDepartment(downIssue, downIssueVo);  //  담당부서 정보 셋팅
                this.setIssueCustomFields(downIssue, downIssueVo);   // 사용자정의필드 정보 세팅
                this.setIssueHistory(downIssue, downIssueVo);   //  이슈 기록 정보 셋팅
                this.setIssueComments(downIssue, downIssueVo);  //  댓글 정보 셋팅
                downIssueVo.setModifyPermissionCheck(issueVo.getModifyPermissionCheck());
                resultList.add(downIssueVo);
            }
@@ -1394,10 +1570,10 @@
        this.setIssueDepartment(issue, issueVo);  //  담당부서 정보 셋팅
        this.setAttachedFiles(issue, issueVo);  //  첨부 파일 정보 셋팅
        this.setIssueCustomFields(issue, issueVo);  //  사용자 정의 필드 값 정보 셋팅
        this.setIssueComments(issue, issueVo);  //  댓글 정보 셋팅
        this.setIssueHistory(issue, issueVo);   //  이슈 기록 정보 셋팅
        this.setRelationIssue(issue, issueVo);        //연관 일감 셋팅
        this.setDownIssues(issue, issueVo); //하위 일감 세팅
        this.setIssueComments(issue, issueVo);  //  댓글 정보 셋팅
        this.setIssueHistory(issue, issueVo);   //  이슈 기록 정보 셋팅
        IssueType issueType = this.issueTypeService.getIssueType(issueVo.getIssueTypeVo().getId()); // 이슈의 이슈유형 객체
        Integer using = issueType.getUsePartner() != null ? issueType.getUsePartner().intValue() : 0; // 이슈유형별로 사용중인 업체/ISP/호스팅 값
@@ -1474,6 +1650,8 @@
                IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(relationIssue.getIssueStatus(), IssueStatusVo.class, "issueStatusType");
                issueStatusVo.setIssueStatusType(relationIssue.getIssueStatus().getIssueStatusType().toString());
                issueRelationVo.setIssueStatusVo(issueStatusVo);
                issueRelationVo.setModifyPermissionCheck(issueVo.getModifyPermissionCheck());
                this.setRegister(relationIssue, relIssueVo); // 등록자
                this.setIssueDepartment(relationIssue, relIssueVo);  //  담당부서 정보 셋팅
@@ -1579,7 +1757,7 @@
    //  이슈 기록 정보를 셋팅한다.
    private void setIssueHistory(Issue issue, IssueVo issueVo) {
        issueVo.setIssueHistoryVos(this.issueHistoryService.findIssueHistory(issue.getId()));
        issueVo.setIssueHistoryVos(this.issueHistoryService.findIssueHistory(issue));
    }
    // 사용자 정의 필드 값이 같은 이슈 찾기
@@ -1591,10 +1769,15 @@
        List<Issue> resultIssueVos = Lists.newArrayList();
        String comma = ",";
        List<String> userValues = Lists.newArrayList();
        if (issueCustomFieldValueForms.size() > 0) {
            IssueCustomFieldValueFormComparator comp = new IssueCustomFieldValueFormComparator();
            Collections.sort(issueCustomFieldValueForms, comp);
            String concatUseValue = "";
            for (int i = 0; i < issueCustomFieldValueForms.size(); i++) {
                IssueCustomFieldValueForm issueCustomFieldValueForm = issueCustomFieldValueForms.get(i);
                userValues.add(issueCustomFieldValueForm.getUseValue());
                if (i > 0) {
                    concatUseValue = concatUseValue.concat(comma);
                }
@@ -1603,6 +1786,7 @@
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            issueCustomFieldValueCondition.setUseValues(userValues);
            issueCustomFieldValueCondition.setIssueTypeId(issueApiform.getIssueTypeId());
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            if (results != null && results.size() > 0) {
@@ -1638,21 +1822,28 @@
            List<Issue> issues = Lists.newArrayList();
            for (Issue issueVo : issue) {
                issueForm.setId(issueVo.getId());
                issueForm.setTitle(issueVo.getTitle());
                // 자동 종료 상태 설정이 되어 있지 않으면 오류발생
                Issue modifyIssue = this.modifyIssueForApi(user, issueForm, files);
                Issue parentIssue = modifyIssue.getParentIssue();
                IssueType issueType = modifyIssue.getIssueType();
                IssueStatus issueStatus = issueType.getIssueStatus();
                Set<IssueTypeApiEndStatus> issueTypeApiEndStatuses = issueType.getIssueTypeApiEndStatuses();
                IssueTypeApiEndStatus issueStatus = null;
                if (issueTypeApiEndStatuses != null && issueTypeApiEndStatuses.size() > 0) {
                    issueStatus = issueTypeApiEndStatuses.iterator().next();
                } else {
                    throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_COMPLETE_ISSUE_STATUS_NOT_EXIST));
                }
                if (parentIssue != null) {
                    if (issueStatus == null) {
                        throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_COMPLETE_ISSUE_STATUS_NOT_EXIST));
                    }
                    IssueCondition issueCondition = new IssueCondition(issueVo.getId(), parentIssue.getId());
                    List<Map<String, Object>> results = this.issueMapper.findNotCompleteByParentIssueId(issueCondition);
                    // 하위 일감이 모두 종료 상태일때 상위 일감도 종료 처리
                    if (results == null || results.size() == 0) {
                        parentIssue.setIssueStatus(issueType.getIssueStatus());
                        parentIssue.setIssueStatus(issueStatus.getIssueStatus());
                        this.issueRepository.saveAndFlush(parentIssue);
                    }
                }
@@ -1719,6 +1910,13 @@
    // 이슈 수정(API용)
    private Issue modifyIssueForApi(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        CheckIssueData checkIssueData = this.checkIssue(user, issueForm);
        if (issueForm.getComment() != null && !issueForm.getComment().equals("")) { //댓글 추가
            IssueCommentForm issueCommentForm = new IssueCommentForm();
            issueCommentForm.setDescription(issueForm.getComment());
            issueCommentForm.setIssueId(issueForm.getId());
            this.issueCommentService.addIssueComment(issueCommentForm, user);
        }
        // 이슈 이력 남기기
        this.addIssueHistoryModify(user, issueForm, checkIssueData, multipartFiles);
@@ -1823,11 +2021,11 @@
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_MODIFY));
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        return issue;
    }
@@ -2091,6 +2289,19 @@
        this.verifyIssueModifyPermission(issue, user);
        IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId());
        if (issueStatus.getIssueStatusType().toString().equals("CLOSE")) {
            List<String> downIssuesStatus = issueForm.getDownIssuesStatus();
            if (downIssuesStatus != null && downIssuesStatus.size() > 0) {
                for (String downIssueStatus : downIssuesStatus) {
                    if (!downIssueStatus.equals("CLOSE")) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_MODIFY_STATUS));
                    }
                }
            }
        }
        //  이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다.
        this.issueStatusService.checkNextIssueStatus(issue, issueStatus);
        //  변경 이력 정보 추출
@@ -2684,11 +2895,8 @@
        excelInfo.setFileName(this.messageAccessor.message("common.registerExcelIssue")); // 엑셀로 이슈 등록하기
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.title"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 제목
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.content"), 40, ExportExcelAttrVo.ALIGN_CENTER)); // 내용
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.projectKey"), 10, ExportExcelAttrVo.ALIGN_LEFT)); // 프로젝트 키
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.issueType"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 이슈 타입
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.priority"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 우선순위
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.importance"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.department"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 담당부서
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.startDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 시작일
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.endDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 종료일
        //  프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다.
@@ -2712,7 +2920,7 @@
    //  엑셀 import 로 이슈를 등록한다.
    @Override
    @Transactional
    public void importExcel(MultipartFile multipartFile) throws Exception {
    public void importExcel(IssueForm issueForm, MultipartFile multipartFile) throws Exception {
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
@@ -2723,19 +2931,16 @@
            //  업로드 파일 확장자 체크
            this.verifyMultipartFileExtension(multipartFile);
            Map<String, Project> projectMaps = new HashMap<>(); //  프로젝트 모음
            Map<String, IssueType> issueTypeMaps = new HashMap<>(); //  이슈 타입 모음
            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, DepartmentVo> 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, departmentMaps, customFieldMaps, issueNumberMaps, issueTypeCustomFieldMaps, issueStatusReadyMaps);
            this.IssueAttributeMapToList(priorityMaps, severityMaps, departmentMaps, customFieldMaps, issueTypeCustomFieldMaps);
            //  0.237 - 0.230
            List<IssueForm> issueForms = Lists.newArrayList();
@@ -2778,7 +2983,14 @@
                //  1번 헤더부터 데이터 영역
                if (rowIndex > 1) {
                    //  이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다.
                    issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, headers));
                    IssueForm newIssueForm = this.setIssueFormToExcelField(row, (rowIndex + 1), priorityMaps, severityMaps, departmentMaps, customFieldMaps, headers);
                    ConvertUtil.copyProperties(issueForm, newIssueForm);
                    issueForms.add(newIssueForm);
                }
            }
@@ -2789,7 +3001,46 @@
            //  이슈 등록
            this.issueMapper.insertBatch(issueForms);
//            this.issueMapper.insertBatch(issueForms);
            for (IssueForm saveIssueForm : issueForms) {
                Issue issue = new Issue();
                ConvertUtil.copyProperties(saveIssueForm, issue);
                IssueType issueType = this.issueTypeService.getIssueType(saveIssueForm.getIssueTypeId());
                Workflow workflow = issueType.getWorkflow();
                Project project = this.projectService.getProject(saveIssueForm.getProjectId());
                Long issueNumber = this.issueNumberGeneratorService.generateIssueNumber(project);
                IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow);
                issue.setPriority(this.priorityService.getPriority(saveIssueForm.getPriorityId()));
                issue.setSeverity(this.severityService.getSeverity(saveIssueForm.getSeverityId()));
                issue.setIssueStatus(issueStatus);
                issue.setIssueType(issueType);
                issue.setProject(project);
                issue.setIssueNumber(issueNumber);
                issue.setParentIssue(this.getIssue(saveIssueForm.getParentIssueId()));
                issue = this.issueRepository.saveAndFlush(issue);
                saveIssueForm.setId(issue.getId());
                IssueDepartment issueDepartment = new IssueDepartment();
                issueDepartment.setIssue(issue);
                issueDepartment.setWorkspace(workspace);
                List<Long> departmentsIds = this.workflowDepartmentService.findFirstDepartmentIds(workflow);
                for (Long departmentId : departmentsIds) {
                    issueDepartment.setDepartment(this.departmentService.getDepartment(departmentId));
                }
                issue.addIssueDepartment(issueDepartment);
                saveIssueForm.setIssueStatusId(issueStatus.getId());
            }
            //  0.416 - 0.439
            //  1.373 ~ 1.394
@@ -2819,7 +3070,8 @@
            //  reverse index 업데이트
            this.issueMapper.updateBatch(issueForms);
            //  증가된 이슈 번호를 업데이트 한다.
            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
//            issueNumberMaps.put(issueForm.getProjectId(), issueForm.getProjectId());
//            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
        }
    }
@@ -2928,51 +3180,8 @@
    }
    //  이슈의 주요 속성을 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, 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();
        for (Project project : projects) {
            projectIds.add(project.getId());
            //  해당 프로젝트에서 생성되는 다음 이슈 번호를 생성해온다.
            issueNumberMaps.put(project.getId(), this.issueNumberGeneratorService.generateIssueNumber(project));
            projectMaps.put(project.getProjectKey(), project);
            for (IssueTypeCustomField issueTypeCustomField : project.getIssueTypeCustomFields()) {
                //  빠르게 찾기 위해 이슈 타입 아이디 + 사용자 정의 필드 아이디를 키로 한다.
                String makeKey = issueTypeCustomField.getIssueType().getId().toString() + issueTypeCustomField.getCustomField().getId().toString();
                issueTypeCustomFieldMaps.put(makeKey, issueTypeCustomField.getId());
            }
            //  프로젝트에 참여하는 사용자 정보
            List<Map<String, Object>> users = this.userService.findProjectMember(project);
            Map<String, Object> userMap = new HashMap<>();
            //  사용자 정보를 Map 에 저장
            for (Map<String, Object> user : users) {
                userMap.put(CommonUtil.decryptAES128(MapUtil.getString(user, "account")), MapUtil.getLong(user, "userId"));
            }
            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"));
            }
        }
        //  이슈 유형을 바로 찾을 수 있게 준비
        List<IssueType> issueTypes = this.issueTypeService.findByWorkspaceId();
        for (IssueType issueType : issueTypes) {
            issueTypeMaps.put(issueType.getName(), issueType);
            IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            issueStatusReadyMaps.put(issueType.getId().toString(), issueStatus);
        }
    private void IssueAttributeMapToList(Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps,Map<String, Long> issueTypeCustomFieldMaps) {
        //  우선순위를 바로 찾을 수 있게 준비
        List<Priority> priorities = this.priorityService.findByWorkspaceId();
@@ -2994,8 +3203,9 @@
    }
    //  엑셀 필드에 있는 정보를 이슈 form 으로 옮긴다.
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, IssueStatus> issueStatusReadyMaps, 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, List<String> headers) {
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, Priority> priorityMaps,
                                               Map<String, Severity> severityMaps, Map<String, DepartmentVo> departmentMaps,
                                               Map<String, CustomField> customFieldMaps, List<String> headers) {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
@@ -3019,40 +3229,33 @@
                    break;
                case 2:    //  프로젝트 키와 이슈 번호
                    project = this.setIssueFormProjectKeyAndIssueNumber(cell, issueForm, projectMaps, issueNumberMaps, rowIndex);
                    break;
                case 3:
                    //  이슈 타입을 IssueForm 에 저장한다.
                    this.setIssueFormIssueType(cell, issueTypeMaps, issueForm, rowIndex);
                    //  이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다.
                    this.setIssueFormIssueStatus(issueStatusReadyMaps, issueForm, rowIndex);
                    break;
                case 4:
                case 2:
                    //  우선순위를 IssueForm 에 저장한다.
                    this.setIssueFormPriority(cell, priorityMaps, issueForm, rowIndex);
                    break;
                case 5:
                case 3:
                    //  중요도를 IssueForm 에 저장한다.
                    this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex);
                    break;
                case 6:
                    //  담당부서를 IssueForm 에 저장한다.
                /*case 6:
                    //  담당자를 IssueForm 에 저장한다.
                    this.setIssueFormAssignee(cell, userMaps, issueForm, project);
                    break;
                case 7:
                    break;*/
                case 4:
                    //  시작일을 IssueForm 에 저장한다.
                    this.setIssueFormPeriod(cell, issueForm, true, rowIndex);
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, true, rowIndex);
                    }
                    break;
                case 8:
                case 5:
                    //  종료일을 IssueForm 에 저장한다.
                    this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    }
                    break;
                default:
                    //  8번 이상부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    //  9번 부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
            }
        }
@@ -3072,17 +3275,6 @@
        //  제목 유효성 체크
        this.verifyTitle(title);
        issueForm.setTitle(title);
    }
    //  프로젝트 키, 이슈 고유 번호, 담당자를 IssueForm 에 저장한다.
    private Project setIssueFormProjectKeyAndIssueNumber(Cell cell, IssueForm issueForm, Map<String, Project> projectMaps, Map<Long, Long> issueNumberMaps, int rowIndex) {
        //  프로젝트 아이디를 IssueForm 에 저장한다.
        Project project = this.setIssueFormProject(cell, projectMaps, issueForm, rowIndex);
        //  이슈 고유 번호를 IssueForm 에 저장한다.
        this.setIssueFormIssueNumber(issueForm, issueNumberMaps, project, rowIndex);
        return project;
    }
    //  프로젝트 아이디를 IssueForm 에 저장한다.
@@ -3118,34 +3310,6 @@
        issueNumberMaps.put(project.getId(), ++issueNumber);  //  이슈 번호를 1씩 증가 시킨다.
    }
    //  이슈 타입을 IssueForm 에 저장한다.
    private void setIssueFormIssueType(Cell cell, Map<String, IssueType> issueTypeMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_IS_NULL, rowIndex));
        }
        IssueType issueType = issueTypeMaps.get(CommonUtil.convertExcelStringToCell(cell));
        if (issueType == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TYPE_NOT_EXIST, rowIndex));
        }
        issueForm.setIssueTypeId(issueType.getId());
    }
    //  이슈 타입에 연결된 워크플로우의 상태 속성 '대기' 인 상태를 issueForm 에 저장한다.
    private void setIssueFormIssueStatus(Map<String, IssueStatus> issueStatusReadyMaps, IssueForm issueForm, int rowIndex) {
        IssueStatus issueStatus = issueStatusReadyMaps.get(issueForm.getIssueTypeId().toString());
        if (issueStatus == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_STATUS_READY_NOT_EXIST, rowIndex));
        }
        issueForm.setIssueStatusId(issueStatus.getId());
    }
    //  우선순위를 IssueForm 에 저장한다.
    private void setIssueFormPriority(Cell cell, Map<String, Priority> priorityMaps, IssueForm issueForm, int rowIndex) {
@@ -3181,27 +3345,30 @@
        issueForm.setSeverityId(severity.getId());
    }
    //  담당부서를 IssueForm 에 저장한다.
    private void setIssueFormAssignee(Cell cell, Map<String, Object> userMaps, IssueForm issueForm, Project project) {
        if (cell != null) {
            String[] splitAssignee = CommonUtil.convertExcelStringToCell(cell).split("#");
            Map<String, Object> userMap = (Map<String, Object>) MapUtil.getObject(userMaps, project.getProjectKey());
            List<Long> departmentIds = Lists.newArrayList();
            List<Long> userIds = Lists.newArrayList();
            for (String account : splitAssignee) {
                if (MapUtil.getLong(userMap, account) != null) {
                    departmentIds.add(MapUtil.getLong(userMap, account));
                    userIds.add(MapUtil.getLong(userMap, account));
                }
            }
            issueForm.setDepartmentIds(departmentIds);
            issueForm.setUserIds(userIds);
        }
    }
    //  시작일, 종료일을 IssueForm 에 저장한다.
    private void setIssueFormPeriod(Cell cell, IssueForm issueForm, Boolean checkStartDate, int rowIndex) {
        if (cell != null && !cell.toString().equals("")) {
            //  값이 공백이면 중지
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
            Date startDate;
@@ -3228,7 +3395,7 @@
        }
    }
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.-
    private void setIssueFormCustomFieldValue(Cell cell, Map<String, CustomField> customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) {
        if (cell != null) {
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
@@ -3565,28 +3732,31 @@
    @Transactional
    @Override
    public void modifyParentIssue(IssueForm issueDownForm) {
        Issue issue = this.getIssue(issueDownForm.getId()); //하위 이슈
        //Issue issue = this.getIssue(issueDownForm.getId()); //하위 이슈
        Long newParentIssueId = issueDownForm.getParentIssueId(); //변경할 하위이슈의 상위이슈
        StringBuilder sb = new StringBuilder();
        Issue parentIssue = issue.getParentIssue(); //변경 전 하위이슈의 상위이슈
        if(parentIssue != null && parentIssue.getId().equals(newParentIssueId)){ //변경 전 하위이슈의 상위이슈가 존재 할 경우
            this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb);
            this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString());
        }
        for (Long downId : issueDownForm.getIds()) {
            Issue issue = this.getIssue(downId);
        if (newParentIssueId != null) { // 추가 할 경우
            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);
            Issue parentIssue = issue.getParentIssue(); //변경 전 하위이슈의 상위이슈
            if(parentIssue != null && parentIssue.getId().equals(newParentIssueId)){ //변경 전 하위이슈의 상위이슈가 존재 할 경우
                this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb);
                this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString());
            }
            if (newParentIssueId != null) { // 추가 할 경우
                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);
        }
        this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString()); //parentIssue = myIssue(기록은 현재 상세페이지에 해야하니까)
        this.issueRepository.saveAndFlush(issue);
    }
    @Override
@@ -3604,4 +3774,17 @@
            resJsonData.put(Constants.RES_KEY_CONTENTS, usePartnerVos);
        }
    }
    @Override
    public void findReadyDepartments(Map<String, Object> resJsonData, DepartmentCondition condition, Pageable pageable) {
        IssueType issueType = this.issueTypeService.getIssueType(condition.getIssueTypeId());
        if (issueType != null) {
            //  이슈 상태 유형이 '대기' 인 이슈 상태 가져오기
            IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            condition.setIssueStatusId(issueStatus.getId());
            condition.setWorkflowId(issueType.getWorkflow().getId());
        }
        List<Map<String, Object>> departmentVos = this.departmentMapper.findByIssueStatusId(condition);
        resJsonData.put(Constants.RES_KEY_CONTENTS, departmentVos);
    }
}