OWL ITS + 탐지시스템(인터넷 진흥원)
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -1,7 +1,6 @@
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;
@@ -13,6 +12,7 @@
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.DepartmentMapper;
import kr.wisestone.owl.mapper.IssueMapper;
import kr.wisestone.owl.mapper.ProjectMapper;
import kr.wisestone.owl.repository.IssueRelationRepository;
@@ -80,6 +80,15 @@
    @Autowired
    private ApiTokenService apiTokenService;
    @Autowired
    private CompanyFieldService companyFieldService;
    @Autowired
    private IspFieldService ispFieldService;
    @Autowired
    private HostingFieldService hostingFieldService;
    @Autowired
    private CommonConfiguration configuration;
@@ -175,7 +184,13 @@
    private SimpMessagingTemplate simpMessagingTemplate;
    @Autowired
    private UserDepartmentService userDepartmentService;
    @Autowired
    private UserDepartmentRepository userDepartmentRepository;
    @Autowired
    private DepartmentMapper departmentMapper;
    @Autowired
    private WorkflowDepartmentRepository workflowDepartmentRepository;
@@ -196,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) {
@@ -211,23 +235,16 @@
        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());
        if (issueApiForm.getApiType().equals(IssueApiForm.ApiType.add)) {
            // 이슈 상태가 지정되어 있지 않을 경우 워크플로우 대기 상태 값으로 지정
            List<Long> departmentIds = this.workflowDepartmentService.findFirstDepartmentIds(workflow);
            if (departmentIds != null && departmentIds.size() > 0) {
                for (Long departmentId : departmentIds) {
                    issueForm.addDepartmentId(departmentId);
                }
            }
        } else if (issueApiForm.getIssueStatusId() == null){
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_EXIST));
        }
        // 프로젝트 입력
@@ -257,23 +274,25 @@
            // 상위일감에 사용할 중복값 설정
            List<CustomFieldApiOverlap> customFieldApiOverlaps = this.customFieldApiOverlapService.find(user.getId(), issueApiForm.getIssueTypeId());
            if (customFieldApiOverlaps == null || customFieldApiOverlaps.size() == 0){
                throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_OVERLAP_SETTING_NOT_EXIST));
            }
            for(int i=0; i < customFieldApiOverlaps.size() ; i++ ){
                CustomFieldApiOverlap customFieldApiOverlap = customFieldApiOverlaps.get(i);
                issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId());
            }
//            if (customFieldApiOverlaps == null || customFieldApiOverlaps.size() == 0){
//                throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_OVERLAP_SETTING_NOT_EXIST));
//            }
            if (customFieldApiOverlaps != null && customFieldApiOverlaps.size() > 0) {
                for (int i = 0; i < customFieldApiOverlaps.size(); i++) {
                    CustomFieldApiOverlap customFieldApiOverlap = customFieldApiOverlaps.get(i);
                    issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId());
                }
            // 중복된 이슈검색
            List<Issue> issues = this.findIssue(issueApiForm, customFieldApiOverlaps, user.getId());
            int size = issues.size();
            if (size > 0) {
                Issue targetIssue = issues.get(0);
                if (targetIssue.getParentIssue() != null) {
                    issueForm.setParentIssueId(targetIssue.getParentIssue().getId());
                } else {
                    issueForm.setParentIssueId(targetIssue.getId());
                // 중복된 이슈검색
                List<Issue> issues = this.findIssue(issueApiForm, customFieldApiOverlaps, user.getId());
                int size = issues.size();
                if (size > 0) {
                    Issue targetIssue = issues.get(0);
                    if (targetIssue.getParentIssue() != null) {
                        issueForm.setParentIssueId(targetIssue.getParentIssue().getId());
                    } else {
                        issueForm.setParentIssueId(targetIssue.getId());
                    }
                }
            }
@@ -281,6 +300,8 @@
            // 사용자 정의 필드 설정
            issueForm.setIssueCustomFields(issueApiForm.getCustomFieldValues());
            //  같은 도메인 업체 찾기
            this.findCompanyField(issueForm);
            // api 입력값 적용
            ConvertUtil.copyProperties(issueApiForm, issueForm);
@@ -289,6 +310,51 @@
        } else {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR));
        }
    }
    private void 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);
            }
        }
    }
@@ -309,10 +375,11 @@
        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());
            Issue issue = addIssue(user, parentIssueForm, null);
@@ -464,6 +531,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.getIssueCompanyFields());
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm.getIssueIspFields());
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm.getIssueHostingFields());
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
        List<Map<String, Object>> convertFileMaps = this.convertMultipartFileToFile(multipartFiles);
        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;
    }
    //  연관이슈를 생성한다.
@@ -700,8 +852,22 @@
            for(UserDepartment myDepartment : myDepartments){
                myDepartmentIds.add(myDepartment.getDepartmentId());
            }
        } else {
            myDepartmentIds.add(-1L);
        }
        issueCondition.setMyDepartmentIds(myDepartmentIds);
    }
    void SetAllDepartmentId(IssueCondition issueCondition){
        List<Long> departmentIds = Lists.newArrayList();
        List<Map<String, Object>> departmentList = this.departmentMapper.find(null);
        if(departmentList != null && departmentList.size() > 0){
            for(Map<String, Object> department : departmentList){
                departmentIds.add((Long) department.get("id"));
            }
        }
        issueCondition.setMyDepartmentIds(departmentIds);
    }
    void SetWorkflowDepartment(List<IssueVo> issueVos){
@@ -761,14 +927,15 @@
        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);
                || 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);
            /*results = this.issueMapper.findByDepartment(issueCondition);
            totalCount = this.issueMapper.countByDepartment(issueCondition);*/
        }
        results = this.issueMapper.find(issueCondition);
        totalCount = this.issueMapper.count(issueCondition);
        //  튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9
@@ -789,10 +956,11 @@
        this.setMapToIssueVo(results, issueVos, issueCondition, user);
        if (issueCondition.getTree()) {
            this.setDownIssues(issueVos);
            this.setParentIssue(issueVos);
            this.setDownIssues(user, issueVos);
            this.setRelationIssues(issueVos);
        }
        this.setCountDownIssues(results, issueVos);
        this.setCountDownIssues(issueVos);
        this.SetWorkflowDepartment(issueVos); //워크플로우에 설정한 담당부서 가져오기
@@ -806,13 +974,34 @@
    }
    // 하위 이슈 세팅
    private void setDownIssues(List<IssueVo> issueVos) {
    // 하위 이슈 세팅(재귀)
    private void setDownIssues(User user, List<IssueVo> issueVos) {
        for(IssueVo issueVo : issueVos) {
            List<Issue> downIssues = this.issueRepository.findByParentIssueId(issueVo.getId());
            List<IssueVo> downIssueVos = Lists.newArrayList();
            IssueCondition issueCondition = new IssueCondition();
            issueCondition.addIssueIds(String.valueOf(issueVo.getId()));
            for(Issue downIssue : downIssues){
                issueVo.addIssueDownVo(ConvertUtil.copyProperties(downIssue, IssueVo.class));
                IssueVo addIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class);
                addIssueVo.setIssueTypeId(downIssue.getIssueType().getId());
                downIssueVos.add(addIssueVo);
            }
            issueVo.setIssueDownVos(downIssueVos);
            if (downIssueVos.size() > 0) {
                this.setDownIssues(user, downIssueVos);
            }
            //  이슈 사용자 정보 추가
            //this.setIssueUserList(issueVos, issueCondition);
            this.setIssueDepartmentList(issueVos, issueCondition, user);
            //  등록자 정보 추가
            this.setRegister(issueVos);  //  담당자 정보 셋팅
            //  사용자 정의 필드 정보 추가
            this.setIssueCustomFieldValue(issueVos, issueCondition);
            //워크플로우에 설정한 담당부서 가져오기
            this.SetWorkflowDepartment(issueVos);
        }
    }
@@ -826,11 +1015,25 @@
        }
    }
    // 상위 이슈 체크
    private void setParentIssue(List<IssueVo> issueVos) {
        for(IssueVo issueVo : issueVos) {
            if(issueVo.getParentIssueId() != null) {
                Issue parentIssue = this.getIssue(issueVo.getParentIssueId());
                //issueVo.setParentIssueVo(ConvertUtil.copyProperties(parentIssue, IssueVo.class));
                if(parentIssue.getIssueCustomFieldValues() == null || parentIssue.getIssueCustomFieldValues().size() == 0){
                    issueVo.setIssueCustomFieldValueVos(null);
                }
                ConvertUtil.copyProperties(parentIssue, issueVo);
            }
        }
    }
    @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")); //하위이슈 가져오기
    public void setCountDownIssues(List<IssueVo> issueVos) {
        for (IssueVo issueVo : issueVos){
            List<Issue> downIssues = this.issueRepository.findByParentIssueId(issueVo.getId()); //하위이슈 가져오기
            if(downIssues != null && downIssues.size() > 0){ //상위이슈 가지고 있는 애들이 있으면
                int downIssueAllCount = 0;// 하위이슈 전체 카운트
                int downIssueCount = 0;// 하위이슈 미완료 카운트
@@ -849,12 +1052,8 @@
                        downIssueCount ++;
                    }
                    for(IssueVo issueVo : issueVos){
                        if(issueVo.getId().equals(parentIssueVo.getId())){
                            issueVo.setDownIssueCount(downIssueCount);
                            issueVo.setDownIssueAllCount(parentIssueVo.getDownIssueAllCount());
                        }
                    }
                    issueVo.setDownIssueCount(downIssueCount);
                    issueVo.setDownIssueAllCount(parentIssueVo.getDownIssueAllCount());
                }
            }
        }
@@ -867,15 +1066,19 @@
    public void findApiIssue(ApiMonitorCondition apiMonitorCondition, Map<String, Object> resJsonData) {
        IssueTypeCondition issueTypeCondition = new IssueTypeCondition();
        issueTypeCondition.setIsApi(Issue.IS_API_YES);
        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());
        List<Date> searchDates = Lists.newArrayList();
        if (apiMonitorCondition.getSearchPeriod().equals(DateUtil.CUSTOM_INPUT)) {
            Date startDate = DateUtil.convertStrToDate(apiMonitorCondition.getSearchStartDate(), "yyyy-MM-dd");
            Date endDate = DateUtil.addDays(DateUtil.convertStrToDate(apiMonitorCondition.getSearchEndDate(), "yyyy-MM-dd"), 1);
            searchDates = CommonUtil.findSearchPeriod(startDate, endDate);
        } else {
            searchDates = CommonUtil.findSearchPeriod(apiMonitorCondition.getSearchPeriod());
        }
        //  날짜가 검색되지 않았으면 오류
        if (searchDates.size() < 1) {
@@ -1084,7 +1287,9 @@
        //  프로젝트를 선택하지 않았으면 해당 업무 공간에서 참여하고 있는 프로젝트를 찾는다.
        if (condition.getProjectIds().size() < 1) {
            List<Map<String, Object>> projects = null;
            if (this.userWorkspaceService.checkWorkspaceManager(user)) {
            UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
            if (this.userWorkspaceService.checkWorkspaceManager(user)
                    || MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) {
                projects = this.projectMapper.findByWorkspaceManagerAll(projectCondition);
            } else  {
                projects = this.projectService.findByWorkspaceIdAndIncludeProjectAll(projectCondition);
@@ -1193,7 +1398,7 @@
            }
            //  이슈 수정 권한을 갖고 있는지 확인
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) {
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user, null)) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
@@ -1230,7 +1435,7 @@
            }
            //  이슈 수정 권한을 갖고 있는지 확인
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user)) {
            if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user, issueVo.getDepartmentVos())) {
                issueVo.setModifyPermissionCheck(Boolean.TRUE);
            }
        }
@@ -1245,6 +1450,7 @@
        if (issueCondition.getId() != null) {
            Issue issue = this.getIssue(issueCondition.getId());
            issueVo = ConvertUtil.copyProperties(issue, IssueVo.class);
            User user = this.webAppUtil.getLoginUserObject();
            switch (issueCondition.getDeep()) {
                case "01": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당부서, 첨부파일, 사용자 정의 필드 정보를 셋팅한다.
@@ -1265,7 +1471,7 @@
                    break;
                case "02": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보, 댓글, 기록을 셋팅한다.
                    this.setIssueDetail(issueVo, issue);    //  이슈 상세 정보를 셋팅한다.
                    this.setIssueDetail(issueVo, issue, user);    //  이슈 상세 정보를 셋팅한다.
                    this.setIssueTableConfigs(issue, issueVo);
                    issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
                    break;
@@ -1326,7 +1532,7 @@
    //  이슈 상세 정보를 셋팅한다.
    @Override
    @Transactional(readOnly = true)
    public void setIssueDetail(IssueVo issueVo, Issue issue) {
    public void setIssueDetail(IssueVo issueVo, Issue issue, User user) {
        issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
        issueVo.setIssueTypeVo(ConvertUtil.copyProperties(issue.getIssueType(), IssueTypeVo.class));
        IssueStatusVo issueStatusVo = ConvertUtil.copyProperties(issue.getIssueStatus(), IssueStatusVo.class, "issueStatusType");
@@ -1361,8 +1567,12 @@
        this.setIssueCompanyField(issue, issueVo);  //업체 정보 세팅
        this.setIssueIspField(issue, issueVo);  //ISP 정보 세팅
        this.setIssueHostingField(issue, issueVo);  //HOSTING 정보 세팅
        this.setParentIssue(issue,issueVo); //상위 이슈 정보 세팅
        //  이슈 수정 권한을 갖고 있는지 확인
        if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user, issueVo.getDepartmentVos())) {
            issueVo.setModifyPermissionCheck(Boolean.TRUE);
        }
    }
    //  상위일감 정보 추가
@@ -1470,6 +1680,15 @@
            DepartmentVo departmentVo = ConvertUtil.copyProperties(issueDepartment.getDepartment(), DepartmentVo.class);
            departmentVo.setByName(departmentVo.getDepartmentName());
            departmentVos.add(departmentVo);
            List<UserDepartment> userDepartments = this.userDepartmentRepository.findByDepartmentId(departmentVo.getId());
            if (userDepartments != null && userDepartments.size() > 0) {
                for (UserDepartment userDepartment : userDepartments) {
                    if (userDepartment.getUserId().equals(this.webAppUtil.getLoginId())){
                        issueVo.setModifyPermissionCheck(Boolean.TRUE);
                    }
                }
            }
        }
        issueVo.setDepartmentVos(departmentVos);
    }
@@ -1576,11 +1795,10 @@
                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) {
                    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);
                    // 하위 일감이 모두 종료 상태일때 상위 일감도 종료 처리
@@ -1653,39 +1871,41 @@
    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();
        // 이슈 이력 남기기
        this.addIssueHistoryModify(user, issueForm, checkIssueData, multipartFiles);
        // db에 저장
        return this.saveIssue(issueForm, checkIssueData);
    }
    private void addIssueHistoryModify(User user, IssueForm issueForm, CheckIssueData checkIssueData, List<MultipartFile> multipartFiles) {
        //  변경 이력 정보 추출
        StringBuilder detectIssueChange = this.issueHistoryService.detectIssueChange(issueForm, checkIssueData, multipartFiles);
        //  프로젝트가 변경되면 이슈 넘버를 새로 따야 한다.
        this.checkChangeProject(checkIssueData.getProject(), issue);
        this.checkChangeProject(checkIssueData.getProject(), checkIssueData.getIssue());
        //  이슈 유형이 변경되었는지 확인하고 변경되었다면 이슈 상태 속성이 '대기' 인 이슈 상태로 교체한다.
        if (this.checkChangeIssueType(issueType, issueStatus, issue)) {
            issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
        if (this.checkChangeIssueType(checkIssueData.getIssueType(), checkIssueData.getNewIssueStatus(), checkIssueData.getIssue())) {
            checkIssueData.setNewIssueStatus(this.issueStatusService.findByIssueStatusTypeIsReady(checkIssueData.getIssueType().getWorkflow()));
            //  이슈 상태 변경 이력 남기기 - 이력을 남기기 위해 issueForm 에 issueStatus Id 값을 저장.
            issueForm.setIssueStatusId(issueStatus.getId());
            this.issueHistoryService.detectIssueStatus(issue, issueForm, detectIssueChange, oldIssueStatus, issueStatus);
            issueForm.setIssueStatusId(checkIssueData.getNewIssueStatus().getId());
            this.issueHistoryService.detectIssueStatus(checkIssueData.getIssue(), issueForm, detectIssueChange, checkIssueData.getOldIssueStatus(), checkIssueData.getNewIssueStatus());
        }
        // db에 저장
        issue = this.saveIssue(issueForm, checkIssueData);
//        checkIssueData.setIssue(this.saveIssue(issueForm, checkIssueData));
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString());
            this.issueHistoryService.addIssueHistory(checkIssueData.getIssue(), 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();
@@ -1875,7 +2095,7 @@
    //  이슈 수정 권한 체크
    private void verifyIssueModifyPermission(Issue issue, User user) {
        //  이슈 수정 권한을 갖고 있는지 확인
        if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue), user)) {
        if (!this.checkHasPermission(ConvertUtil.copyProperties(issue, IssueVo.class), this.getIssueUserVos(issue), user, this.getIssueDepartmentVos(issue))) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_MODIFY_PERMISSION));
        }
@@ -1900,18 +2120,41 @@
        return userVos;
    }
    //  이슈에서 담당자 정보를 추출한다.
    private List<DepartmentVo> getIssueDepartmentVos(Issue issue) {
        List<DepartmentVo> departmentVos = Lists.newArrayList();
        Set<IssueDepartment> issueDepartments = issue.getIssueDepartments();
        try {
            for (IssueDepartment issueDepartment : issueDepartments) {
                Department department = issueDepartment.getDepartment();
                DepartmentVo departmentVo = ConvertUtil.copyProperties(department, DepartmentVo.class);
                departmentVos.add(departmentVo);
            }
        } catch (Exception ex) {
        }
        return departmentVos;
    }
    //  이슈 수정 권한을 갖고 있는지 확인
    private boolean checkHasPermission(IssueVo issueVo, List<UserVo> issueUserVos, User user) {
    private boolean checkHasPermission(IssueVo issueVo, List<UserVo> issueUserVos, User user, List<DepartmentVo> departmentVos) {
        boolean hasPermission = false;
        //  업무 공간 관리자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.WORKSPACE_MANAGER, issueVo, null, null, user);
        //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.PROJECT_MANAGER, issueVo, null, null, user);
        //  이슈 관리자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ISSUE_MANAGER, issueVo, null, null, user);
        //   이슈 등록자일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.REGISTER, issueVo, null, null, user);
        //  이슈 담당자일 경우 수정 권한을 갖는다. => 담당부서로 수정 - 체크
        //  이슈 담당자일 경우 수정 권한을 갖는다.
        //hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ASSIGNEE, issueVo, issueUserVos);
        //  이슈 담당부서일 경우 수정 권한을 갖는다.
        hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.DEPARTMENT, issueVo, null, departmentVos, user);
        //  담당자가 없으면 모든 사용자가 수정 권한을 갖는다.
        //hasPermission = this.checkIssueModifyPermission(hasPermission, Issue.ALL_ISSUE_MANAGER, issueVo, null, null, user);
@@ -1921,7 +2164,7 @@
    }
    //  이슈 수정 권한을 확인한다.
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos, List<DepartmentVo> issueDepartmentVos, User user) {
    private boolean checkIssueModifyPermission(Boolean hasPermission, String checkType, IssueVo issueVo, List<UserVo> issueUserVos, List<DepartmentVo> departmentVos, User user) {
        if (!hasPermission) {
            switch (checkType) {
                case Issue.WORKSPACE_MANAGER:  //  업무 공간 관리자
@@ -1933,6 +2176,11 @@
                    Issue issue = this.getIssue(issueVo.getId());
                    //  프로젝트 관리자일 경우 해당 프로젝트에 등록된 이슈는 수정 권한을 갖는다.
                    hasPermission = this.projectRoleUserService.checkProjectManager(issue.getProject(), user);
                    break;
                case Issue.ISSUE_MANAGER:    //  이슈 관리자
                    UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
                    hasPermission = MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE);
                    break;
                case Issue.REGISTER:   //  이슈 등록자
@@ -1956,17 +2204,22 @@
                case Issue.DEPARTMENT:
                    //  담당부서가 없으면 모든 사용자가 수정 권한을 갖는다.
                    if (issueDepartmentVos.size() < 1) {
                    /*if (userDepartmentVos.size() < 1) {
                        hasPermission = true;
                        break;
                    }
                    //   이슈 담당부서 여부 확인
                    /*for (DepartmentVo issueDepartmentVo : issueDepartmentVos) {
                        if (issueDepartmentVo.getId().equals()) {
                            hasPermission = true;
                            break;
                        }
                    }*/
                    //   이슈 담당부서 여부 확인
                    for (DepartmentVo departmentVo : departmentVos) {
                        List<UserDepartment> userDepartments = this.userDepartmentService.findByDepartmentId(departmentVo.getId());
                        if(userDepartments != null && userDepartments.size() > 0) {
                            for (UserDepartment userDepartment : userDepartments) {
                                if (userDepartment.getUserId().equals(user.getId())){
                                    hasPermission = true;
                                    break;
                                }
                            }
                        }
                    }
                    break;
            }
        }
@@ -1989,6 +2242,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);
        //  변경 이력 정보 추출
@@ -2626,7 +2892,7 @@
            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<>();  //  이슈 번호 모음
@@ -2676,7 +2942,7 @@
                //  1번 헤더부터 데이터 영역
                if (rowIndex > 1) {
                    //  이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다.
                    issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, customFieldMaps, issueNumberMaps, headers));
                    issueForms.add(this.setIssueFormToExcelField(row, (rowIndex + 1), issueStatusReadyMaps, projectMaps, issueTypeMaps, priorityMaps, severityMaps, userMaps, departmentMaps, customFieldMaps, issueNumberMaps, headers));
                }
            }
@@ -2827,7 +3093,7 @@
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    private void IssueAttributeMapToList(Map<String, Project> projectMaps, Map<String, IssueType> issueTypeMaps, Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, Object> userMaps, Map<String, Object> departmentMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, Map<String, Long> issueTypeCustomFieldMaps, Map<String, IssueStatus> issueStatusReadyMaps) {
                                         Map<String, Object> userMaps, Map<String, DepartmentVo> 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();
@@ -2851,16 +3117,7 @@
            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"));
            }
        }
        //  이슈 유형을 바로 찾을 수 있게 준비
@@ -2870,6 +3127,13 @@
            IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(issueType.getWorkflow());
            issueStatusReadyMaps.put(issueType.getId().toString(), issueStatus);
            //  워크플로우에 속해있는 부서 정보
            List<DepartmentVo> departments = this.departmentService.findWorkflowDepartment(issueType.getId());
            //  부서 정보를 저장
            for (DepartmentVo department : departments) {
                departmentMaps.put(department.getDepartmentName(), department);
            }
        }
        //  우선순위를 바로 찾을 수 있게 준비
@@ -2893,7 +3157,7 @@
    //  엑셀 필드에 있는 정보를 이슈 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) {
            Priority> priorityMaps, Map<String, Severity> severityMaps, Map<String, Object> userMaps, Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps, Map<Long, Long> issueNumberMaps, List<String> headers) {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
@@ -2937,9 +3201,13 @@
                    //  중요도를 IssueForm 에 저장한다.
                    this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex);
                    break;
                /*case 6:
                    //  담당자를 IssueForm 에 저장한다.
                    this.setIssueFormAssignee(cell, userMaps, issueForm, project);
                    break;*/
                case 6:
                    //  담당부서를 IssueForm 에 저장한다.
                    this.setIssueFormAssignee(cell, userMaps, issueForm, project);
                    this.setIssueFormDepartment(cell, departmentMaps, issueForm, project);
                    break;
                case 7:
                    //  시작일을 IssueForm 에 저장한다.
@@ -2950,7 +3218,7 @@
                    this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    break;
                default:
                    //  8번 이상부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    //  9번 부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
            }
        }
@@ -3079,20 +3347,39 @@
        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.setUserIds(userIds);
        }
    }
    //  담당부서를 IssueForm 에 저장한다.
    private void setIssueFormDepartment(Cell cell, Map<String, DepartmentVo> departmentMaps, IssueForm issueForm, Project project) {
        if (cell != null) {
            String[] splitDepartment = CommonUtil.convertExcelStringToCell(cell).split("#");
            //  값이 공백이면 중지
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
            List<Long> departmentIds = Lists.newArrayList();
            for (String department : splitDepartment) {
                DepartmentVo departmentVo = departmentMaps.get(department);
                departmentIds.add(departmentVo.getId());
            }
            issueForm.setDepartmentIds(departmentIds);
        }
    }
@@ -3100,6 +3387,12 @@
    //  시작일, 종료일을 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;
@@ -3262,45 +3555,36 @@
    //  이슈를 템플릿에 따라 파트너 담당자에게 메일로 발송한다.
    @Override
    @Transactional(readOnly = true)
    public void sendIssueEmailPartners(IssueForm issueForm) {
        if (issueForm.getSendEmails().size() < 1) {
    public void sendIssueEmailPartners(EmailTemplateForm emailTemplateForm) {
        if (emailTemplateForm.getSendEmails().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER));
        }else if (issueForm.getTemplate() == null){
        }else if (emailTemplateForm.getTemplate() == null){
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SELECT_TEMPLATE));
        } else if (emailTemplateForm.getIssueId() == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_EXIST));
        }
        Issue issue = this.getIssue(issueForm.getId());
        Issue issue = this.getIssue(emailTemplateForm.getIssueId());
        Map<String, Object> issueMap = new HashMap<>();
        //  이슈 정보를 이메일 전송에 사용하기 위해 Map 형태로 변환한다.
        this.makeIssueMapToIssue(issue, issueMap);
        //  발신자 표시
        User user = this.webAppUtil.getLoginUserObject();
        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);
        Locale locale = CommonUtil.getUserLanguage(user.getLanguage());
        String[] sendMails = ConvertUtil.ToArray(emailTemplateForm.getSendEmails());
        for(int i=0; i < sendMails.length; i++) {
            sendMails[i] = CommonUtil.decryptAES128(sendMails[i]);
        }
        //메일 전송 이력 남기기
        this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, issueForm, sb);
        this.systemEmailService.sendEmail(emailTemplateForm.getTitle(), emailTemplateForm.getTemplate(), sendMails, null);
        this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, emailTemplateForm.getSendEmails(), sb);
        this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.SEND, sb.toString());
    }