OWL ITS + 탐지시스템(인터넷 진흥원)
wyu
2021-12-10 debc7d47107b1fc4509593d942d775f4374a3416
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -1,12 +1,12 @@
package kr.wisestone.owl.service.impl;
import com.google.common.collect.Lists;
import com.sun.org.apache.bcel.internal.generic.NEW;
import kr.wisestone.owl.common.ExcelConditionCheck;
import kr.wisestone.owl.common.IssueCustomFieldValueFormComparator;
import kr.wisestone.owl.config.CommonConfiguration;
import kr.wisestone.owl.constant.Constants;
import kr.wisestone.owl.constant.ElasticSearchConstants;
import kr.wisestone.owl.constant.MsgConstants;
import kr.wisestone.owl.constant.UsePartner;
import kr.wisestone.owl.constant.*;
import kr.wisestone.owl.data.CheckIssueData;
import kr.wisestone.owl.domain.*;
import kr.wisestone.owl.domain.enumType.CustomFieldType;
import kr.wisestone.owl.domain.enumType.EmailType;
@@ -15,15 +15,15 @@
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.IssueMapper;
import kr.wisestone.owl.mapper.ProjectMapper;
import kr.wisestone.owl.repository.IssueRelationRepository;
import kr.wisestone.owl.repository.IssueRepository;
import kr.wisestone.owl.repository.UserDepartmentRepository;
import kr.wisestone.owl.repository.WorkflowDepartmentRepository;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.*;
import kr.wisestone.owl.util.DateUtil;
import kr.wisestone.owl.vo.*;
import kr.wisestone.owl.web.condition.IssueCondition;
import kr.wisestone.owl.web.condition.IssueCustomFieldValueCondition;
import kr.wisestone.owl.web.condition.IssueTypeCustomFieldCondition;
import kr.wisestone.owl.web.condition.ProjectCondition;
import kr.wisestone.owl.web.condition.*;
import kr.wisestone.owl.web.form.*;
import kr.wisestone.owl.web.view.ExcelView;
import org.apache.commons.lang3.StringUtils;
@@ -55,10 +55,10 @@
    private IssueRepository issueRepository;
    @Autowired
    private IssueTableConfigService issueTableConfigService;
    private ProjectService projectService;
    @Autowired
    private ProjectService projectService;
    private IssueTableConfigService issueTableConfigService;
    @Autowired
    private IssueStatusService issueStatusService;
@@ -80,15 +80,6 @@
    @Autowired
    private ApiTokenService apiTokenService;
    @Autowired
    private CompanyFieldService companyFieldService;
    @Autowired
    private IspFieldService ispFieldService;
    @Autowired
    private HostingFieldService hostingFieldService;
    @Autowired
    private CommonConfiguration configuration;
@@ -160,7 +151,16 @@
    private UserWorkspaceService userWorkspaceService;
    @Autowired
    private UserLevelService userLevelService;
    @Autowired
    private WorkflowDepartmentService workflowDepartmentService;
    @Autowired
    private IssueRelationService issueRelationService;
    @Autowired
    private IssueRelationRepository issueRelationRepository;
    @Autowired
    private ExcelView excelView;
@@ -173,6 +173,12 @@
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Autowired
    private UserDepartmentRepository userDepartmentRepository;
    @Autowired
    private WorkflowDepartmentRepository workflowDepartmentRepository;
    @Override
    protected JpaRepository<Issue, Long> getRepository() {
@@ -191,11 +197,7 @@
    }
    //  API 를 통해 이슈 추가.
    @Override
    @Transactional
    public Issue addApiIssue(IssueApiForm issueApiForm) {
    private IssueForm convertToIssueForm(IssueApiForm issueApiForm, User user) {
        if (issueApiForm.getIssueTypeId() == null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
        }
@@ -207,6 +209,27 @@
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
        }
        Workflow workflow = issueType.getWorkflow();
        // 이슈 상태가 지정되어 있지 않을 경우 초기값으로 지정
        if (issueApiForm.getIssueStatusId() == null) {
            List<IssueStatusVo> issueStatusVos = issueStatusService.findByWorkflowId(workflow.getId());
            IssueStatusVo issueStatusVo = issueStatusVos.get(0);
            issueApiForm.setIssueStatusId(issueStatusVo.getId());
        }
        // 워크플로우 상태에 따른 담당부서 가져오기
        if (issueApiForm.getIssueStatusId() != null) {
            WorkflowDepartmentCondition workflowDepartmentCondition = new WorkflowDepartmentCondition();
            workflowDepartmentCondition.setIssueStatusId(issueApiForm.getIssueStatusId());
            workflowDepartmentCondition.setWorkflowId(workflow.getId());
            List<WorkflowDepartmentVo> workflowDepartmentVos = this.workflowDepartmentService.find(workflowDepartmentCondition);
            for (WorkflowDepartmentVo workflowDepartmentVo : workflowDepartmentVos) {
                issueForm.addDepartmentId(workflowDepartmentVo.getDepartmentVo().getId());
            }
        }
        // 프로젝트 입력
        Project project = issueType.getProject();
        if (project == null){
@@ -214,14 +237,7 @@
        }
        issueForm.setProjectId(project.getId());
        // 토큰으로 유저 정보 가져오기
        String token = issueApiForm.getToken();
        UserVo userVo = this.apiTokenService.certification(token);
        // 해당 유저 정보가 현재 db에 있는지 확인
        User user = this.userService.getUser(userVo.getId());
        if (user != null) {
            // 기본값 입력하기
            IssueApiDefaultForm issueApiDefaultForm = new IssueApiDefaultForm();
            issueApiDefaultForm.setUserId(user.getId());
@@ -229,18 +245,27 @@
            IssueApiDefault issueApiDefault = this.issueApiDefaultService.find(issueApiDefaultForm);
            if (issueApiDefault != null) {
                ConvertUtil.copyProperties(issueApiDefault, issueForm);
                issueForm.setId(null);
                issueForm.setPriorityId(issueApiDefault.getPriority().getId());
                issueForm.setSeverityId(issueApiDefault.getSeverity().getId());
            }
            // 중복 값 하위 이슈로 처리하기
            // 중복 값 상위 이슈의 하위 이슈로 처리하기
            CustomFieldApiOverlapForm customFieldApiOverlapForm = new CustomFieldApiOverlapForm();
            customFieldApiOverlapForm.setUserId(user.getId());
            customFieldApiOverlapForm.setIssueTypeId(issueForm.getIssueTypeId());
            IssueVo issueVo = this.findIssue(issueApiForm, user.getId());
            if (issueVo != null) {
                issueForm.setParentIssueId(issueVo.getId());
            // 상위일감에 사용할 중복값 설정
            List<CustomFieldApiOverlap> customFieldApiOverlaps = this.customFieldApiOverlapService.find(user.getId(), issueApiForm.getIssueTypeId());
            for(int i=0; i < customFieldApiOverlaps.size() ; i++ ){
                CustomFieldApiOverlap customFieldApiOverlap = customFieldApiOverlaps.get(i);
                issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId());
            }
            List<IssueVo> issueVos = this.findIssue(issueApiForm, customFieldApiOverlaps, user.getId());
            int size = issueVos.size();
            if (size > 0) {
                issueForm.setParentIssueId(issueVos.get(0).getId());
            }
            issueForm.setIsApi(Issue.IS_API_YES);
@@ -251,33 +276,90 @@
            // api 입력값 적용
            ConvertUtil.copyProperties(issueApiForm, issueForm);
            return addIssue(user, issueForm, issueApiForm.getMultipartFiles());
            return issueForm;
        } else {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR));
        }
    }
    // 중복된 상위 이슈 검색
    private IssueVo findIssue(IssueApiForm issueApiForm, Long userId) {
        IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
    private User convertToUser(String token) {
        // 토큰으로 유저 정보 가져오기
        UserVo userVo = this.apiTokenService.certification(token);
        List<CustomFieldApiOverlap> customFieldApiOverlaps = this.customFieldApiOverlapService.find(userId, issueApiForm.getIssueTypeId());
        if (customFieldApiOverlaps != null && customFieldApiOverlaps.size() > 0) {
            for (CustomFieldApiOverlap customFieldApiOverlap : customFieldApiOverlaps) {
                for (IssueCustomFieldValueForm issueCustomFieldValue : issueApiForm.getIssueCustomFieldValues()) {
                    if (customFieldApiOverlap.getCustomField().getId().equals(issueCustomFieldValue.getCustomFieldId())) {
                        issueCustomFieldValueCondition.addUseValues(issueCustomFieldValue.getUseValue());
        // 해당 유저 정보가 현재 db에 있는지 확인
        return this.userService.getUser(userVo.getId());
    }
    //  API 를 통해 이슈 추가.
    @Override
    @Transactional
    public List<Issue> addApiIssue(IssueApiForm issueApiForm) throws CloneNotSupportedException {
        User user = convertToUser(issueApiForm.getToken());
        IssueForm issueForm = this.convertToIssueForm(issueApiForm, user);
        List<Issue> issues = Lists.newArrayList();
        if (issueForm.getParentIssueId() != null) {
            issues.add(addIssue(user, issueForm, issueApiForm.getMultipartFiles()));
        } else {
            // 상위 이슈 추가
            IssueForm parentIssueForm = issueForm.clone();
            parentIssueForm.setUseIssueCustomFields(issueApiForm.getUseIssueCustomFieldIds());
            Issue issue = addIssue(user, parentIssueForm, null);
            issues.add(issue);
            // 하위 이슈 추가
            issueForm.setParentIssueId(issue.getId());
            issues.add(addIssue(user, issueForm, issueApiForm.getMultipartFiles()));
        }
        return issues;
    }
    // 상위 이슈 가져오기
    private IssueVo getParentIssueVo(Long parentIssueId) {
        if (parentIssueId != null) {
            Issue parentIssue = this.getIssue(parentIssueId);
            return ConvertUtil.copyProperties(parentIssue, IssueVo.class);
        }
        return null;
    }
    // 중복된 상위 이슈 검색
    private List<IssueVo> findIssue(IssueApiForm issueApiform, List<CustomFieldApiOverlap> customFieldApiOverlaps, Long userId) {
        List<IssueCustomFieldValueForm> issueCustomFieldValueForms = issueApiform.getIssueCustomFieldValues();
        List<IssueVo> resultIssueVos = Lists.newArrayList();
        String comma = ",";
        if (issueCustomFieldValueForms.size() > 0) {
            String concatUseValue = "";
            int useIdx = 0;
            IssueCustomFieldValueFormComparator comp = new IssueCustomFieldValueFormComparator();
            Collections.sort(issueCustomFieldValueForms, comp);
            for (IssueCustomFieldValueForm issueCustomFieldValueForm : issueCustomFieldValueForms) {
                for(CustomFieldApiOverlap customFieldApiOverlap : customFieldApiOverlaps) {
                    if (customFieldApiOverlap.getCustomField().getId().equals(issueCustomFieldValueForm.getCustomFieldId())) {
                        if (useIdx > 0) {
                            concatUseValue = concatUseValue.concat(comma);
                        }
                        concatUseValue = concatUseValue.concat(issueCustomFieldValueForm.getUseValue());
                        useIdx++;
                    }
                }
            }
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            if (results != null && results.size() > 0) {
                IssueVo issueVo = new IssueVo();
                ConvertUtil.convertMapToObject(results.get(0), issueVo);
                return issueVo;
                for (Map<String, Object> result : results) {
                    resultIssueVos.add(ConvertUtil.convertMapToClass(result, IssueVo.class));
                }
            }
        }
        return null;
        return resultIssueVos;
    }
    //  이슈를 생성한다.
@@ -308,7 +390,7 @@
        //  날짜 유효성 체크
        this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
        //  담당 부서 유효성 체크
        this.verifyIssueDepartment(project, issueForm);
        //this.verifyIssueDepartment(project, issueForm);
        //  이슈 상태 유형이 '대기' 인 이슈 상태 가져오기
        IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
@@ -319,6 +401,10 @@
        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));    //  각 프로젝트의 고유 이슈 번호 생성
@@ -328,7 +414,7 @@
        //  담당자 지정
        //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, project.getWorkspace(), issueForm.getDepartmentIds());
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
        //  ISP 정보 저장
@@ -336,6 +422,91 @@
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        //  첨부 파일 저장
        //  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;
    }
    //  연관이슈를 생성한다.
    @Override
    @Transactional
    public Issue addRelIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return addRelIssue(user, issueForm, multipartFiles);
    }
    //  연관이슈를 생성한다.
    @Override
    @Transactional
    public Issue addRelIssue(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.getIssueCompanyFields());
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -373,11 +544,9 @@
        projectRoleUserMap.put("statuses", Lists.newArrayList("02"));   //  관리자 조회
        //  관리자 정보 셋팅
        List<Map<String, Object>> projectRoleUsers = this.projectRoleUserService.findProjectRoleUser(projectRoleUserMap);
        if (projectRoleUsers != null && !projectRoleUsers.isEmpty()) {
            for (Map<String, Object> projectRoleUser : projectRoleUsers) {
                UserVo userVo = ConvertUtil.convertMapToClass(projectRoleUser, UserVo.class);
                //  이슈 생성 알림 메일 전송
                this.systemEmailService.reservationEmail(new String[]{userVo.getAccount()}, emailType, issueMap);
            }
@@ -408,7 +577,7 @@
            departsBuilder.append(issueDepartment.getDepartment().getDepartmentName());
            departsBuilder.append("\n");
        }
        issueMap.put("departsBuilder", departsBuilder.toString());
        issueMap.put("departments", departsBuilder.toString());
        //  기간
        if (!StringUtils.isEmpty(issue.getStartDate())) {
@@ -512,11 +681,41 @@
        }
    }
    void SetMyDepartmentId(IssueCondition issueCondition){
        Long loginId = issueCondition.getLoginUserId();
        List<Long> myDepartmentIds = Lists.newArrayList();
        List<UserDepartment> myDepartments = this.userDepartmentRepository.findByUserId(loginId);
        if(myDepartments != null && myDepartments.size() > 0){
            for(UserDepartment myDepartment : myDepartments){
                myDepartmentIds.add(myDepartment.getDepartmentId());
            }
        }
        issueCondition.setMyDepartmentIds(myDepartmentIds);
    }
    void SetWorkflowDepartment(List<IssueVo> issueVos){
        for(IssueVo issueVo : issueVos){
            Long issueTypeId = issueVo.getIssueTypeId();
            IssueType issueType = this.issueTypeService.getIssueType(issueTypeId);
            Long workflowId = issueType.getWorkflow().getId();
            List<WorkflowDepartment> workflowDepartmentList = this.workflowDepartmentRepository.findByWorkflowId(workflowId);
            List<Long> workflowDepartmentIds = Lists.newArrayList();
            if(workflowDepartmentList != null && workflowDepartmentList.size()>0){
                for(WorkflowDepartment workflowDepartment : workflowDepartmentList){
                    workflowDepartmentIds.add(workflowDepartment.getDepartment().getId());
                }
            }
            if(issueVo.getIssueTypeId().equals(issueTypeId)){
                issueVo.setWorkflowDepartmentIds(workflowDepartmentIds);
            }
        }
    }
    //  이슈 목록을 조회한다.
    @Override
    @Transactional(readOnly = true)
    public List<IssueVo> findIssue(Map<String, Object> resJsonData,
                                   IssueCondition issueCondition, Pageable pageable) {
    public List<IssueVo> findIssue(Map<String, Object> resJsonData, IssueCondition issueCondition, Pageable pageable) {
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition, Lists.newArrayList("01", "02", "03"), pageable)) {
@@ -542,12 +741,29 @@
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        List<Map<String, Object>> results = this.issueMapper.find(issueCondition);
        issueCondition.setLoginUserId(this.webAppUtil.getLoginId());
        issueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        User user = this.webAppUtil.getLoginUserObject();
        List<Map<String, Object>> results = Lists.newArrayList();
        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_PROJECT_ALL)) {
            results = this.issueMapper.find(issueCondition);
            totalCount = this.issueMapper.count(issueCondition);
        } else{
            this.SetMyDepartmentId(issueCondition);
            results = this.issueMapper.findByDepartment(issueCondition);
            totalCount = this.issueMapper.countByDepartment(issueCondition);
        }
        //  튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
        Long totalCount = this.issueMapper.count(issueCondition);
        //  튜닝 전 - 1.1, 1.1, 1.3, 1.2
        /*serviceStart.stop();
@@ -555,9 +771,13 @@
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
        issueCondition.setIsApi(issueCondition.getIsApi());
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVo(results, issueVos, issueCondition);
        this.setMapToIssueVo(results, issueVos, issueCondition, user);
        this.setCountDownIssues(results, issueVos);
        this.SetWorkflowDepartment(issueVos); //워크플로우에 설정한 담당부서 가져오기
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
        resJsonData.put(Constants.REQ_KEY_PAGE_VO, new ResPage(pageable.getPageNumber(), pageable.getPageSize(),
@@ -567,6 +787,89 @@
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_FIND));
        return issueVos;
    }
    @Override
    @Transactional(readOnly = true)
    public void setCountDownIssues(List<Map<String, Object>> results, List<IssueVo> issueVos) {
        for (Map<String, Object> result : results){
            List<Issue> downIssues = this.issueRepository.findByParentIssueId((Long) result.get("id")); //하위이슈 가져오기
            if(downIssues != null && downIssues.size() > 0){ //상위이슈 가지고 있는 애들이 있으면
                int downIssueAllCount = 0;// 하위이슈 전체 카운트
                int downIssueCount = 0;// 하위이슈 미완료 카운트
                for(Issue downIssue : downIssues){
                    downIssueAllCount ++;
                    Long parentIssueId = downIssue.getParentIssue().getId();
                    Issue parentIssue = this.getIssue(parentIssueId);
                    IssueVo parentIssueVo = ConvertUtil.copyProperties(parentIssue, IssueVo.class);
                    parentIssueVo.setDownIssueAllCount(downIssueAllCount);
                    IssueStatus downIssueStatus = this.issueStatusService.getIssueStatus(downIssue.getIssueStatus().getId());
                    IssueVo downIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class);
                    downIssueVo.setIssueStatusType(downIssueStatus.getIssueStatusType().toString());
                    if(!downIssueVo.getIssueStatusType().equals("CLOSE")){ //미완료 하위이슈 체크
                        downIssueCount ++;
                    }
                    for(IssueVo issueVo : issueVos){
                        if(issueVo.getId().equals(parentIssueVo.getId())){
                            issueVo.setDownIssueCount(downIssueCount);
                            issueVo.setDownIssueAllCount(parentIssueVo.getDownIssueAllCount());
                        }
                    }
                }
            }
        }
    }
    //  이슈 목록을 조회한다(차트용 - 연관일감포함)
    @Override
    @Transactional(readOnly = true)
    public void findApiIssue(ApiMonitorCondition apiMonitorCondition, Map<String, Object> resJsonData) {
        IssueTypeCondition issueTypeCondition = new IssueTypeCondition();
        List<IssueTypeVo> issueTypes = this.issueTypeService.findIssueType(issueTypeCondition);
        // 값이 없을 경우 초기값 입력
        if (StringUtils.isEmpty(apiMonitorCondition.getSearchPeriod())) {
            apiMonitorCondition.setSearchPeriod(DateUtil.LAST_SEVEN_DAYS);
        }
        //  검색 일자를 구한다.
        List<Date> searchDates = CommonUtil.findSearchPeriod(apiMonitorCondition.getSearchPeriod());
        //  날짜가 검색되지 않았으면 오류
        if (searchDates.size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.WIDGET_SEARCH_DATE_NOT_FOUND));
        }
        Long index = 1L;
        List<ApiMonitorVo> apiMonitorVos = Lists.newArrayList();
        for (Date date : searchDates) {
            String onlyDate = DateUtil.convertDateToYYYYMMDD(date);
            issueTypeCondition.setStartDate(onlyDate + " 00:00:00");
            issueTypeCondition.setEndDate(onlyDate + " 23:59:59");
            ApiMonitorVo apiMonitorVo = new ApiMonitorVo();
            apiMonitorVo.setId(index);
            for (IssueTypeVo issueTypeVo : issueTypes) {
                issueTypeCondition.setId(issueTypeVo.getId());
                apiMonitorVo.addIssueTypeCount(this.issueMapper.countByIssueTypeIdAndDate(issueTypeCondition));
                apiMonitorVo.setIsApi(Issue.IS_API_YES);
                apiMonitorVos.add(apiMonitorVo);
            }
            index++;
        }
        Map<String, Object> data = new HashMap<>();
        data.put("issueTypeVos", issueTypes);
        data.put("apiMonitorVos", apiMonitorVos);
        resJsonData.put(Constants.RES_KEY_CONTENTS, data);
    }
    //  이슈 목록을 조회한다(차트용 - 연관일감포함)
    @Override
@@ -623,7 +926,9 @@
        IssueCondition issueCondition = new IssueCondition();
        //  검색 조건을 만든다
        if (!this.makeIssueSearchCondition(issueCondition, projectCondition, pageable)) {
        User user = this.webAppUtil.getLoginUserObject();
        if (!this.makeIssueSearchCondition(user, issueCondition, projectCondition, pageable)) {
            //  이슈 목록을 찾지 못할 경우 기본 정보로 리턴한다.
            this.notFoundIssueList(resJsonData, pageable);
            return Lists.newArrayList();
@@ -667,7 +972,7 @@
    }
    //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
    private void setMapToIssueVo(List<Map<String, Object>> results, List<IssueVo> issueVos, IssueCondition issueCondition) {
    private void setMapToIssueVo(List<Map<String, Object>> results, List<IssueVo> issueVos, IssueCondition issueCondition, User user) {
        for (Map<String, Object> result : results) {
            IssueVo issueVo = ConvertUtil.convertMapToClass(result, IssueVo.class);
            issueVos.add(issueVo);
@@ -676,7 +981,7 @@
        //  이슈 사용자 정보 추가
        //this.setIssueUserList(issueVos, issueCondition);
        this.setIssueDepartmentList(issueVos, issueCondition);
        this.setIssueDepartmentList(issueVos, issueCondition, user);
        //  등록자 정보 추가
        this.setRegister(issueVos);  //  담당자 정보 셋팅
@@ -723,7 +1028,7 @@
    }
    //  검색 조건을 만든다
    private boolean makeIssueSearchCondition(IssueCondition condition, ProjectCondition projectCondition, Pageable pageable) {
    private boolean makeIssueSearchCondition(User user, IssueCondition condition, ProjectCondition projectCondition, Pageable pageable) {
        if (pageable != null) {
            condition.setPage(pageable.getPageNumber() * pageable.getPageSize());
            condition.setPageSize(pageable.getPageSize());
@@ -733,6 +1038,7 @@
        condition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        projectCondition.setWorkspaceId(condition.getWorkspaceId());
        //  프로젝트 키가 존재할 경우 프로젝트 키에 해당하는 프로젝트를 조회하고 검색 조건에 셋팅한다.
        if (!this.getProjectByProjectKey(condition.getProjectKey(), condition)) {
            return false;
@@ -741,7 +1047,9 @@
        //  프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다.
        if (condition.getProjectIds().size() < 1) {
            List<Map<String, Object>> projects = null;
            if (this.userWorkspaceService.checkWorkspaceManager()) {
            UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
            if (this.userWorkspaceService.checkWorkspaceManager(user)
                    || MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE_PROJECT_ALL)) {
                projects = this.projectMapper.findByWorkspaceManagerAll(projectCondition);
            } else  {
                projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
@@ -819,7 +1127,7 @@
    }
    //  이슈 담당자 정보를 셋팅한다.
    private void setIssueUserList(List<IssueVo> issueVos, IssueCondition issueCondition) {
    private void setIssueUserList(List<IssueVo> issueVos, IssueCondition issueCondition, User user) {
        if (issueVos.size() < 1) {
            return;
        }
@@ -850,14 +1158,14 @@
            }
            //  이슈 수정 권한을 갖고 있는지 확인
            if (this.checkHasPermission(issueVo, issueVo.getUserVos())) {
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
    }
    //  이슈 담당부서 정보를 셋팅한다.
    private void setIssueDepartmentList(List<IssueVo> issueVos, IssueCondition issueCondition) {
    private void setIssueDepartmentList(List<IssueVo> issueVos, IssueCondition issueCondition, User user) {
        if (issueVos.size() < 1) {
            return;
        }
@@ -887,7 +1195,7 @@
            }
            //  이슈 수정 권한을 갖고 있는지 확인
            if (this.checkHasPermission(issueVo, issueVo.getUserVos())) {
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
@@ -923,6 +1231,7 @@
                case "02": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보, 댓글, 기록을 셋팅한다.
                    this.setIssueDetail(issueVo, issue);    //  이슈 상세 정보를 셋팅한다.
                    this.setIssueTableConfigs(issue, issueVo);
                    issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
                    break;
            }
@@ -934,28 +1243,49 @@
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo);
    }
    // 테이블 설정 셋팅
    private void setIssueTableConfigs(Issue issue, IssueVo issueVo) {
        Long IssueTypeId = issue.getIssueType().getId();
        for (int tableConfigType : IssueTableConfig.IssueTableTypes) {
            if (tableConfigType != IssueTableConfig.ISSUE_TABLE_TYPE_MAIN) {
                issueVo.addIssueTableConfigVo(createIssueTableConfigVo(IssueTypeId, tableConfigType));
            }
        }
    }
    private IssueTableConfigVo createIssueTableConfigVo(Long issueTypeId, int tableConfigType) {
        IssueTableConfig issueTableConfig = this.issueTableConfigService.findByUserIdAndWorkspaceIdAndIssueTypeIdAndIssueTableType(issueTypeId, tableConfigType);
        if (issueTableConfig != null) {
            return ConvertUtil.copyProperties(issueTableConfig, IssueTableConfigVo.class);
        }
        return new IssueTableConfigVo();
    }
    // 하위 이슈 정보를 셋팅한다
    private void setDownIssues(Issue issue, IssueVo issueVo) {
        List<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId());
        List<IssueVo> downIssueVos = ConvertUtil.convertObjectsToClasses(downIssues, IssueVo.class);
        List<IssueVo> resultList = new ArrayList<>();
        if(downIssues != null && downIssueVos.size()>0){
            for(IssueVo downIssueVo : downIssueVos){
            if(downIssues != null && downIssues.size()>0){
                List<IssueVo> resultList = new ArrayList<>();
                for(Issue downIssue : downIssues){
                    downIssueVo.setTitle(downIssue.getTitle());
                    IssueVo downIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class);
                    downIssueVo.setIssueTypeVo(ConvertUtil.copyProperties(downIssue.getIssueType(), IssueTypeVo.class));
                    downIssueVo.setPriorityVo(ConvertUtil.copyProperties(downIssue.getPriority(), PriorityVo.class));
                    downIssueVo.setSeverityVo(ConvertUtil.copyProperties(downIssue.getSeverity(), SeverityVo.class));
                    //이슈 상태 추가
                    IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(downIssue.getIssueStatus(), IssueStatusVo.class, "issueStatusType");
                    issueStatusVo.setIssueStatusType(downIssue.getIssueStatus().getIssueStatusType().toString());
                    downIssueVo.setIssueStatusVo(issueStatusVo);
                    this.setRegister(downIssue, downIssueVo); // 등록자
                    this.setIssueDepartment(downIssue, downIssueVo);  //  담당부서 정보 셋팅
                    this.setIssueCustomFields(downIssue, downIssueVo);   // 사용자정의필드 정보 세팅
                    resultList.add(downIssueVo);
                }
                resultList.add(downIssueVo);
                issueVo.setIssueDownVos(resultList);
            }
            issueVo.setIssueDownVos(resultList); //프론트에서 List형태로 받아줘서 리스트 형식으로 보내줌
        }else{
            issueVo.setIssueDownVos(null);
        }
    }
    //  이슈 상세 정보를 셋팅한다.
@@ -1046,6 +1376,11 @@
                issueRelationVo.setIssueTypeVo(ConvertUtil.copyProperties(relationIssue.getIssueType(), IssueTypeVo.class));
                issueRelationVo.setPriorityVo(ConvertUtil.copyProperties(relationIssue.getPriority(), PriorityVo.class));
                issueRelationVo.setSeverityVo(ConvertUtil.copyProperties(relationIssue.getSeverity(), SeverityVo.class));
                //이슈 상태 추가
                IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(relationIssue.getIssueStatus(), IssueStatusVo.class, "issueStatusType");
                issueStatusVo.setIssueStatusType(relationIssue.getIssueStatus().getIssueStatusType().toString());
                issueRelationVo.setIssueStatusVo(issueStatusVo);
                this.setRegister(relationIssue, relIssueVo); // 등록자
                this.setIssueDepartment(relationIssue, relIssueVo);  //  담당부서 정보 셋팅
                this.setIssueCustomFields(relationIssue, relIssueVo);   // 사용자정의필드 정보 세팅
@@ -1118,7 +1453,7 @@
    }
    //  이슈(이슈 유형)에 연결된 사용자 정의 필드 정보를 셋팅한다.
    private void setIssueCustomFields(Issue issue, IssueVo issueVo) {
    private IssueVo setIssueCustomFields(Issue issue, IssueVo issueVo) {
        //  해당 프로젝트의 이슈 유형에 연결된 사용자 정의 필드 정보를 가져온다.
        IssueTypeCustomFieldCondition issueTypeCustomFieldCondition = new IssueTypeCustomFieldCondition();
@@ -1130,6 +1465,8 @@
        //  이슈에서 사용된 사용자 정의 필드 값을 가져온다.
        List<IssueCustomFieldValueVo> issueCustomFieldValueVos = this.issueCustomFieldValueService.findByIssueId(issue.getId());
        issueVo.setIssueCustomFieldValueVos(issueCustomFieldValueVos);
        return issueVo;
    }
    //  이슈에 등록된 댓글 정보를 셋팅한다.
@@ -1142,14 +1479,109 @@
        issueVo.setIssueHistoryVos(this.issueHistoryService.findIssueHistory(issue.getId()));
    }
    // 사용자 정의 필드 값이 같은 이슈 찾기
    @Override
    @Transactional
    public List<IssueVo> findIssue(IssueApiForm issueApiform) {
        List<IssueCustomFieldValueForm> issueCustomFieldValueForms = issueApiform.getIssueCustomFieldValues();
        List<IssueVo> resultIssueVos = Lists.newArrayList();
        String comma = ",";
        if (issueCustomFieldValueForms.size() > 0) {
            String concatUseValue = "";
            for (int i = 0; i < issueCustomFieldValueForms.size(); i++) {
                IssueCustomFieldValueForm issueCustomFieldValueForm = issueCustomFieldValueForms.get(i);
                if (i > 0) {
                    concatUseValue = concatUseValue.concat(comma);
                }
                concatUseValue = concatUseValue.concat(issueCustomFieldValueForm.getUseValue());
            }
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            if (results != null && results.size() > 0) {
                for (Map<String, Object> result : results) {
                    resultIssueVos.add(ConvertUtil.convertMapToClass(result, IssueVo.class));
                }
            }
        }
        return resultIssueVos;
    }
    // 리스트에서 해당 아이디를 가지고 있는 이슈 검색
    private IssueVo findIssueVo(List<IssueVo> list, Long id) {
        for (IssueVo issueVo : list) {
            if (id.equals(issueVo.getId())) {
                return issueVo;
            }
        }
        return null;
    }
    // 이슈를 수정한다(api용)
    @Override
    @Transactional
    public List<Issue> modifyIssue(IssueApiForm issueApiForm, List<MultipartFile> files) {
        User user = this.convertToUser(issueApiForm.getToken());
        IssueForm issueForm = this.convertToIssueForm(issueApiForm, user);
        List<IssueVo> issueVos = this.findIssue(issueApiForm);
        if (issueVos != null && issueVos.size() > 0) {
            List<Issue> issue = Lists.newArrayList();
            for (IssueVo issueVo : issueVos) {
                IssueVo parentIssueVo = issueVo.getParentIssueVo();
                issueForm.setId(issueVo.getId());
                // 자동 종료 상태 설정이 되어 있지 않으면 오류발생
                Issue modifyIssue = this.modifyIssueForApi(user, issueForm, files);
                Issue parentIssue = modifyIssue.getParentIssue();
                IssueType issueType = modifyIssue.getIssueType();
                IssueStatus issueStatus = issueType.getIssueStatus();
                if (issueStatus == null) {
                    throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_COMPLETE_ISSUE_STATUS_NOT_EXIST));
                }
                if (parentIssue != null) {
                    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());
                        this.issueRepository.saveAndFlush(parentIssue);
                    }
                }
                issue.add(modifyIssue);
            }
            return issue;
        } else {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.API_ISSUE_NOT_EXIST));
        }
    }
    //  이슈를 수정한다.
    @Override
    @Transactional
    public Issue modifyIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return modifyIssue(user, issueForm, multipartFiles);
    }
    // 수정 데이터가 유효한지 확인
    private CheckIssueData checkIssue(User user, IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId());
        Issue issue = this.getIssue(issueForm.getId());
        IssueStatus oldIssueStatus = issue.getIssueStatus();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        this.verifyIssueModifyPermission(issue, user);
        //  프로젝트 유효성 체크
        Project project = this.projectService.getProject(issueForm.getProjectId());
        //  이슈 상태 유효성 체크
@@ -1168,12 +1600,86 @@
        //  담당자 유효성 체크
        //this.verifyIssueAssignee(project, issueForm);
        //  담당부서 유효성 체크
        this.verifyIssueDepartment(project, issueForm);
        //this.verifyIssueDepartment(project, issueForm);
        Issue issue = this.getIssue(issueForm.getId());
        CheckIssueData checkIssueData = new CheckIssueData();
        checkIssueData.setIssue(issue);
        checkIssueData.setProject(project);
        checkIssueData.setOldIssueStatus(oldIssueStatus);
        checkIssueData.setNewIssueStatus(issueStatus);
        checkIssueData.setIssueType(issueType);
        checkIssueData.setPriority(priority);
        checkIssueData.setSeverity(severity);
        return  checkIssueData;
    }
    // 이슈 수정(API용)
    private Issue modifyIssueForApi(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        CheckIssueData checkIssueData = this.checkIssue(user, issueForm);
        Issue issue = checkIssueData.getIssue();
        Project project = checkIssueData.getProject();
        IssueType issueType = checkIssueData.getIssueType();
        IssueStatus oldIssueStatus = checkIssueData.getOldIssueStatus();
        IssueStatus issueStatus = checkIssueData.getNewIssueStatus();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issue, issueForm, project, issueStatus, issueType, priority, severity, multipartFiles);
        StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issueForm, checkIssueData, multipartFiles);
        //  프로젝트가 변경되면 이슈 넘버를 새로 따야 한다.
        this.checkChangeProject(checkIssueData.getProject(), issue);
        //  이슈 유형이 변경되었는지 확인하고 변경되었다면 이슈 상태 속성이 '대기' 인 이슈 상태로 교체한다.
        if (this.checkChangeIssueType(issueType, issueStatus, issue)) {
            issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            //  이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장.
            issueForm.setIssueStatusId(issueStatus.getId());
            this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus);
        }
        // db에 저장
        issue = this.saveIssue(issueForm, checkIssueData);
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString());
        }
        //  사용자 시스템 기능 사용 정보 수집
        UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class);
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_MODIFY));
        return issue;
    }
    private Issue saveIssue(IssueForm issueForm, CheckIssueData checkIssueData) {
        Issue issue = checkIssueData.getIssue();
        ConvertUtil.copyProperties(issueForm, issue, "id");
        issue.setProject(checkIssueData.getProject());
        issue.setIssueStatus(checkIssueData.getNewIssueStatus());
        issue.setIssueType(checkIssueData.getIssueType());
        issue.setPriority(checkIssueData.getPriority());
        issue.setSeverity(checkIssueData.getSeverity());
        issue.setStartDate(issueForm.getStartDate());
        issue.setCompleteDate(issueForm.getCompleteDate());
        return this.issueRepository.saveAndFlush(issue);
    }
    //  이슈를 수정한다.
    @Override
    @Transactional
    public Issue modifyIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        CheckIssueData checkIssueData = this.checkIssue(user, issueForm);
        Issue issue = checkIssueData.getIssue();
        IssueStatus oldIssueStatus = checkIssueData.getOldIssueStatus();
        Project project = checkIssueData.getProject();
        IssueStatus issueStatus = checkIssueData.getNewIssueStatus();
        IssueType issueType = checkIssueData.getIssueType();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issueForm, checkIssueData, multipartFiles);
        //  프로젝트가 변경되면 이슈 넘버를 새로 따야 한다.
        this.checkChangeProject(project, issue);
@@ -1183,30 +1689,21 @@
            issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            //  이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장.
            issueForm.setIssueStatusId(issueStatus.getId());
            this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, issueStatus);
            this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus);
        }
        ConvertUtil.copyProperties(issueForm, issue, "id");
        issue.setProject(project);
        issue.setIssueStatus(issueStatus);
        issue.setIssueType(issueType);
        issue.setPriority(priority);
        issue.setSeverity(severity);
        issue.setStartDate(issueForm.getStartDate());
        issue.setCompleteDate(issueForm.getCompleteDate());
        this.issueRepository.saveAndFlush(issue);
        issue = this.saveIssue(issueForm, checkIssueData);
        //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  담당부서 지정
        if(issueForm.getDepartmentIds().size()>0){
            this.issueDepartmentService.modifyIssueDepartment(issue, project.getWorkspace(), issueForm.getDepartmentIds());
            this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        }
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
        //  첨부 파일 저장 - 비동기로 작동
        this.attachedFileService.addAttachedFile(convertFileMaps, issue, this.webAppUtil.getLoginUser().getAccount());
        this.attachedFileService.addAttachedFile(convertFileMaps, issue, user.getAccount());
        //  삭제된 첨부파일 처리
        this.attachedFileService.removeAttachedFiles(issueForm.getRemoveFiles());
        //  텍스트 에디터에 첨부한 파일을 이슈와 연결
@@ -1215,10 +1712,11 @@
        this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields());
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.MODIFY, detectIssueChange.toString());
            this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString());
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_MODIFY));
        UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class);
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_MODIFY));
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm.getIssueCompanyFields());
@@ -1340,11 +1838,9 @@
    }
    //  이슈 수정 권한 체크
    private void verifyIssueModifyPermission(Long issueId) {
        Issue issue = this.getIssue(issueId);
    private void verifyIssueModifyPermission(Issue issue, User user) {
        //  이슈 수정 권한을 갖고 있는지 확인
        if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue))) {
        if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue), user)) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_MODIFY_PERMISSION));
        }
@@ -1354,48 +1850,58 @@
    private List<UserVo> getIssueUserVos(Issue issue) {
        List<UserVo> userVos = Lists.newArrayList();
        for (IssueUser issueUser : issue.getIssueUsers()) {
            UserVo userVo = ConvertUtil.copyProperties(issueUser.getUser(), UserVo.class, "password");
            userVos.add(userVo);
        Set<IssueUser> issueUsers = issue.getIssueUsers();
        try {
            for (IssueUser issueUser : issueUsers) {
                User user = issueUser.getUser();
                UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class, "password");
                userVos.add(userVo);
            }
        } catch (Exception ex) {
        }
        return userVos;
    }
    //  이슈 수정 권한을 갖고 있는지 확인
    private boolean checkHasPermission(IssueVo issueVo, List<UserVo> issueUserVos) {
    private boolean checkHasPermission(IssueVo issueVo, List<UserVo> issueUserVos, User user) {
        boolean hasPermission = false;
        //  업무 공간 관리자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null, null, user);
        //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null, null, user);
        //   이슈 등록자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null, null);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null, null, user);
        //  이슈 담당자일 경우 수정 권한을 갖는다. => 담당부서로 수정 - 체크
        //hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ASSIGNEE, issueVo, issueUserVos);
        //  담당자가 없으면 모든 사용자가 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ALL_ISSUE_MANAGER, issueVo, null, null, user);
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ALL_PROJECT_MANAGER, issueVo, null, null, user);
        return hasPermission;
    }
    //  이슈 수정 권한을 확인한다.
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos, List<DepartmentVo> issueDepartmentVos) {
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos, List<DepartmentVo> issueDepartmentVos, User user) {
        if (!hasPermission) {
            switch (checkType) {
                case Issue.WORKSPACE_MANAGER:  //  업무 공간 관리자
                    //  업무 공간 관리자일 경우 수정 권한을 갖는다.
                    hasPermission = this.userWorkspaceService.checkWorkspaceManager();
                    hasPermission = this.userWorkspaceService.checkWorkspaceManager(user);
                    break;
                case Issue.PROJECT_MANAGER:    //  프로젝트 관리자
                    Issue issue = this.getIssue(issueVo.getId());
                    //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
                    hasPermission = this.projectRoleUserService.checkProjectManager(issue.getProject());
                    hasPermission = this.projectRoleUserService.checkProjectManager(issue.getProject(), user);
                    break;
                case Issue.REGISTER:   //  이슈 등록자
                    hasPermission = issueVo.getRegisterId().equals(this.webAppUtil.getLoginId());
                    hasPermission = issueVo.getRegisterId().equals(user.getId());
                    break;
                case Issue.ASSIGNEE:
@@ -1406,7 +1912,7 @@
                    }
                    //   이슈 담당자 여부 확인
                    for (UserVo issueUserVo : issueUserVos) {
                        if (issueUserVo.getId().equals(this.webAppUtil.getLoginId())) {
                        if (issueUserVo.getId().equals(user.getId())) {
                            hasPermission = true;
                            break;
                        }
@@ -1436,20 +1942,22 @@
    //  이슈 상태 변경
    @Override
    @Transactional
    public void modifyIssueStatus(IssueForm issueForm) {
    public void modifyIssueStatus(IssueForm issueForm, User user) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = new StringBuilder();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        Issue issue = this.getIssue(issueForm.getId());
        IssueStatus oldIssueStatus = issue.getIssueStatus();
        this.verifyIssueModifyPermission(issue, user);
        IssueStatus issueStatus = this.issueStatusService.getIssueStatus(issueForm.getIssueStatusId());
        //  이슈 상태를 변경할 때 선택한 이슈 상태로 변경할 수 있는지 확인한다.
        this.issueStatusService.checkNextIssueStatus(issue, issueStatus);
        //  변경 이력 정보 추출
        this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, issueStatus);
        this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus);
        issue.setIssueStatus(issueStatus);
        this.issueRepository.saveAndFlush(issue);
@@ -1483,13 +1991,15 @@
    @Override
    @Transactional
    public void modifyIssueUser(IssueForm issueForm) {
        User user = this.webAppUtil.getLoginUserObject();
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = new StringBuilder();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        Issue issue = this.getIssue(issueForm.getId());
        this.verifyIssueModifyPermission(issue, user);
        issue.setProject(this.projectService.getProject(issueForm.getProjectId()));
        //  변경 이력 정보 추출
@@ -1518,13 +2028,14 @@
    @Override
    @Transactional
    public void modifyIssueDepartment(IssueForm issueForm) {
        User user = this.webAppUtil.getLoginUserObject();
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = new StringBuilder();
        //  이슈 수정 권한 체크
        this.verifyIssueModifyPermission(issueForm.getId());
        Issue issue = this.getIssue(issueForm.getId());
        this.verifyIssueModifyPermission(issue, user);
        issue.setProject(this.projectService.getProject(issueForm.getProjectId()));
        //  변경 이력 정보 추출
@@ -1556,6 +2067,7 @@
    @Transactional
    public void removeIssues(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        User user = this.webAppUtil.getLoginUserObject();
        this.workspaceService.checkUseWorkspace();
        if (issueForm.getRemoveIds().size() < 1) {
@@ -1566,22 +2078,97 @@
        List<Issue> removeIssues = Lists.newArrayList();
        for (Long issueId : issueForm.getRemoveIds()) {
            Issue issue = this.issueRemoves(issueId);
            //하위이슈 체크
            List<Issue> downIssues = this.issueRepository.findByParentIssueId(issueId);
            if(downIssues != null && downIssues.size() > 0){
                for(Issue downIssue : downIssues){
                    if(downIssue.getParentIssue() != null){
                        downIssue.setParentIssue(null);
                    }
                }
            }
            Issue issue = this.issueRemoves(issueId, user);
            removeIssues.add(issue);
        }
        if (removeIssues.size() > 0) {
            //this.issueRepository.deleteAll(removeIssues);
        }
        /*if (removeIssues.size() > 0) {
            this.issueRepository.deleteAll(removeIssues);
        }*/
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE));
    }
    private Issue issueRemoves(Long issueId) {
        Issue issue = this.getIssue(issueId);
    //  이슈를 삭제한다.
    @Override
    @Transactional
    public void removeAllIssues(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        User user = this.webAppUtil.getLoginUserObject();
        this.workspaceService.checkUseWorkspace();
        if (issueForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_REMOVE_NOT_SELECT));
        }
        List<Issue> removeIssues = Lists.newArrayList();
        for (Long issueId : issueForm.getRemoveIds()) {
            //하위이슈 체크
            List<Issue> downIssues = this.issueRepository.findByParentIssueId(issueId);
            if(downIssues != null && downIssues.size() > 0){
                for(Issue downIssue : downIssues){
                    Long downIssueId = downIssue.getId();
                    downIssue = this.issueRemoves(downIssueId, user);
                    removeIssues.add(downIssue);
                }
            }
            Issue issue = this.issueRemoves(issueId, user);
            removeIssues.add(issue);
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE));
    }
    //  하위이슈를 삭제한다.
    @Override
    @Transactional
    public void removeDownIssues(IssueForm issueForm) {
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        User user = this.webAppUtil.getLoginUserObject();
        this.workspaceService.checkUseWorkspace();
        if (issueForm.getRemoveIds().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_REMOVE_NOT_SELECT));
        }
        List<Issue> removeIssues = Lists.newArrayList();
        Long downIssueId = 0L;
        for (Long issueId : issueForm.getRemoveIds()) {
            //삭제 할 이슈의 하위이슈 체크
            List<Issue> downIssues = this.issueRepository.findByParentIssueId(issueId);
            if(downIssues != null && downIssues.size() > 0){
                for(Issue downIssue : downIssues){
                    downIssueId = downIssue.getId();
                }
            }
            Issue issue = this.issueRemoves(downIssueId, user);
            removeIssues.add(issue);
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE));
    }
    private Issue issueRemoves(Long issueId, User user) {
        Issue issue = null;
        if(issueId != null){
            issue = this.getIssue(issueId);
        }
        //  이슈 수정 권한을 갖고 있는지 확인
        this.verifyIssueModifyPermission(issueId);
        this.verifyIssueModifyPermission(issue, user);
        //  이슈 첨부 파일을 삭제한다.
        if (issue.getAttachedFiles().size() > 0) {
@@ -1592,6 +2179,17 @@
            }
            //  첨부파일 삭제
            this.attachedFileService.removeAttachedFiles(attachedFileIds);
        }
        // 지울 이슈가 연관이슈인지 체크 후 연관이슈 테이블에서도 삭제한다.
        List<IssueRelation> issueRelationList = this.issueRelationRepository.findByRelationIssueId(issueId);
        if (issueRelationList != null && issueRelationList.size() > 0) {
            for(IssueRelation issueRelation : issueRelationList){
                StringBuilder sb = new StringBuilder();
                issueHistoryService.detectRelationIssue(IssueHistoryType.DELETE, issueRelation, sb);
                issueHistoryService.addIssueHistory(issueRelation.getIssue(), IssueHistoryType.MODIFY, sb.toString());
                this.issueRelationRepository.delete(issueRelation);
            }
        }
        //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
@@ -1745,7 +2343,8 @@
        issueCondition.setIssueIds(Lists.newArrayList());
        //  Map 에 있는 데이터를 IssueVo 데이터로 변환한다.
        this.setMapToIssueVo(results, issueVos, issueCondition);
        User user = this.webAppUtil.getLoginUserObject();
        this.setMapToIssueVo(results, issueVos, issueCondition, user);
        //  IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
        List<Map<String, String>> convertExcelViewToIssueMaps = this.convertExcelViewToIssueVos(issueVos);
@@ -1819,6 +2418,10 @@
        for(IssueIsp issueIsp : issue.getIssueIspFields()){
            IssueIspVo issueIspVo = ConvertUtil.copyProperties(issueIsp, IssueIspVo.class);
            issueIspVo.setId(issueIsp.getId());
            IspField ispField = issueIsp.getIspField();
            if (ispField != null) {
                issueIspVo.setIspId(ispField.getId());
            }
            issueIspVos.add(issueIspVo);
        }
        issueVo.setIssueIspVos(issueIspVos);
@@ -1831,6 +2434,11 @@
        for(IssueHosting issueHosting : issue.getIssueHostingFields()){
            IssueHostingVo issueHostingVo = ConvertUtil.copyProperties(issueHosting, IssueHostingVo.class);
            issueHostingVo.setId(issueHosting.getId());
            HostingField hostingField = issueHosting.getHostingField();
            if (hostingField != null) {
                issueHostingVo.setHostingId(hostingField.getId());
            }
            issueHostingVos.add(issueHostingVo);
        }
        issueVo.setIssueHostingVos(issueHostingVos);
@@ -1889,15 +2497,11 @@
                        result.put("customField_" + issueCustomFieldValueVo.getCustomFieldVo().getId().toString(), useValue + ", " + issueCustomFieldValueVo.getUseValue());
                    }
                }
                //업체, ISP, 호스팅 추가
                results.add(result);
            } catch (Exception e) {
                log.error("엑셀 다운로드 오류 발생");
            }
        }
        return results;
    }
@@ -1908,10 +2512,12 @@
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        this.workspaceService.checkUseWorkspace();
        User user = this.webAppUtil.getLoginUserObject();
        for (Long issueId : issueForm.getIds()) {
            issueForm.setId(issueId);
            //  이슈 상태 변경
            this.modifyIssueStatus(issueForm);
            this.modifyIssueStatus(issueForm, user);
        }
        // 담당 부서 수정
@@ -2615,15 +3221,52 @@
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL));
        //this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND, issueMap, null);
        this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND, issueMap, null);
    }
        if(issueForm.getTemplate().equals("SEND1")){
            this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND_1, issueMap, null);
        }else if(issueForm.getTemplate().equals("SEND2")){
            this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND_2, issueMap, null);
        }else if(issueForm.getTemplate().equals("SEND3")){
            this.systemEmailService.directEmail(issueForm.getSendEmails().toArray(new String[issueForm.getSendEmails().size()]), EmailType.ISSUE_SEND_3, issueMap, null);
    //  이슈를 템플릿에 따라 파트너 담당자에게 메일로 발송한다.
    @Override
    @Transactional(readOnly = true)
    public void sendIssueEmailPartners(IssueForm issueForm) {
        if (issueForm.getSendEmails().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER));
        }else if (issueForm.getTemplate() == null){
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SELECT_TEMPLATE));
        }
        Issue issue = this.getIssue(issueForm.getId());
        Map<String, Object> issueMap = new HashMap<>();
        //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
        this.makeIssueMapToIssue(issue, issueMap);
        //  발신자 표시
        UserVo toUser = this.webAppUtil.getLoginUser();
        issueMap.put("toUser", toUser.getName() + "(" + CommonUtil.decryptAES128(toUser.getAccount()) + ")");
        // 이슈 링크
        String projectKey = issue.getProject().getProjectKey();
        Long IssueNumber = issue.getIssueNumber();
        String link = this.configuration.getEmailSendUrl() + "/#/issues/issueList?projectKey=" + projectKey + "&issueNumber=" + IssueNumber.toString();
        issueMap.put("issueLink", link);
        issueMap.put("projectLink", link);
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL));
        StringBuilder sb = new StringBuilder();
        if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_1.toString())){
            this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_1, issueMap, null);
        }else if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_2.toString())){
            this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_2, issueMap, null);
        }else if(issueForm.getTemplate().equals(EmailType.ISSUE_SEND_3.toString())){
            this.systemEmailService.directEmail(ConvertUtil.ToArray(issueForm.getSendEmails()), EmailType.ISSUE_SEND_3, issueMap, null);
        }
        //메일 전송 이력 남기기
        this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, issueForm, sb);
        this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.SEND, sb.toString());
    }
    //  예약 발생 이슈를 실행한다
@@ -2795,16 +3438,21 @@
    @Override
    public void modifyParentIssue(IssueForm issueDownForm) {
        Issue issue = this.getIssue(issueDownForm.getId()); //하위 이슈
        Issue parentIssue = issue.getParentIssue(); //변경 전 하위이슈의 상위이슈
        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());
        }
        if (newParentIssueId != null) { // 추가 할 경우
            parentIssue = this.getIssue(newParentIssueId); //상위이슈(myIssue)
            issue.setParentIssue(parentIssue); //myIssue를 하위이슈의 상위이슈로 set
            this.issueHistoryService.detectDownIssues(IssueHistoryType.ADD, issue, sb); //issue = 하위이슈
        } else  {
        } else{
            // 삭제 할 경우
            this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb);
            issue.setParentIssue(null);
@@ -2829,4 +3477,4 @@
            resJsonData.put(Constants.RES_KEY_CONTENTS, usePartnerVos);
        }
    }
}
}