OWL ITS + 탐지시스템(인터넷 진흥원)
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -11,15 +11,14 @@
import kr.wisestone.owl.domain.enumType.EmailType;
import kr.wisestone.owl.domain.enumType.IssueHistoryType;
import kr.wisestone.owl.domain.enumType.IssueStatusType;
import kr.wisestone.owl.exception.ApiAuthException;
import kr.wisestone.owl.exception.ApiParameterException;
import kr.wisestone.owl.exception.OwlRuntimeException;
import kr.wisestone.owl.mapper.DepartmentMapper;
import kr.wisestone.owl.mapper.IssueMapper;
import kr.wisestone.owl.mapper.IssueRelationMapper;
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.repository.*;
import kr.wisestone.owl.service.*;
import kr.wisestone.owl.util.*;
import kr.wisestone.owl.util.DateUtil;
@@ -47,7 +46,10 @@
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
import static kr.wisestone.owl.domain.enumType.CustomFieldType.*;
@Service
public class IssueServiceImpl extends AbstractServiceImpl<Issue, Long, JpaRepository<Issue, Long>> implements IssueService {
@@ -86,6 +88,9 @@
    @Autowired
    private CompanyFieldService companyFieldService;
    @Autowired
    private CompanyFieldCategoryService companyFieldCategoryService;
    @Autowired
    private IspFieldService ispFieldService;
@@ -181,6 +186,15 @@
    private IssueMapper issueMapper;
    @Autowired
    private IssueCompanyRepository issueCompanyRepository;
    @Autowired
    private IssueIspRepository issueIspRepository;
    @Autowired
    private IssueHostingRepository issueHostingRepository;
    @Autowired
    private ExcelConditionCheck excelConditionCheck;
    @Autowired
@@ -200,6 +214,9 @@
    @Autowired
    private IssueRelationMapper issueRelationMapper;
    @Autowired
    private WorkflowTransitionService workflowTransitionService;
    @Override
    protected JpaRepository<Issue, Long> getRepository() {
@@ -229,14 +246,14 @@
    private IssueForm convertToIssueForm(IssueApiForm issueApiForm, User user) {
        if (issueApiForm.getIssueTypeId() == null) {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
            throw new ApiParameterException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
        }
        IssueForm issueForm = ConvertUtil.copyProperties(issueApiForm, IssueForm.class);
//        issueForm.setFiles(issueApiForm.getFiles());
        IssueType issueType = this.issueTypeService.getIssueType(issueApiForm.getIssueTypeId());
        if (issueType == null){
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
            throw new ApiParameterException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_ISSUE_TYPE_ERROR));
        }
        Workflow workflow = issueType.getWorkflow();
@@ -250,13 +267,16 @@
                }
            }
        } else if (issueApiForm.getIssueStatusId() == null){
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_EXIST));
            throw new ApiParameterException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_EXIST));
        } else if (!this.workflowTransitionService.contains(issueApiForm.getIssueStatusId(), workflow.getId())) {
            //이슈 상태 유효성 확인
            throw new ApiParameterException(this.messageAccessor.getMessage(MsgConstants.API_ISSUE_STATUS_NOT_EXIST_IN_WORKFLOW));
        }
        // 프로젝트 입력
        Project project = issueType.getProject();
        if (project == null){
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_PROJECT_ERROR));
            throw new ApiParameterException(this.messageAccessor.getMessage(MsgConstants.API_PARAMETER_PROJECT_ERROR));
        }
        issueForm.setProjectId(project.getId());
@@ -289,8 +309,8 @@
                    issueApiForm.addUseIssueCustomFieldId(customFieldApiOverlap.getCustomField().getId());
                }
                // 중복된 이슈검색
                List<Issue> issues = this.findIssue(issueApiForm, customFieldApiOverlaps, user.getId());
                // 중복된 상위 이슈검색
                List<Issue> issues = this.findIssue(issueApiForm, issueForm, customFieldApiOverlaps, user.getId());
                int size = issues.size();
                if (size > 0) {
                    Issue targetIssue = issues.get(0);
@@ -307,7 +327,6 @@
            // 사용자 정의 필드 설정
            issueForm.setIssueCustomFields(issueApiForm.getCustomFieldValues());
            //  같은 도메인 업체 찾기
            this.findCompanyField(issueForm);
            // api 입력값 적용
            ConvertUtil.copyProperties(issueApiForm, issueForm);
@@ -315,43 +334,56 @@
            return issueForm;
        } else {
            throw new OwlRuntimeException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR));
            throw new ApiAuthException(this.messageAccessor.getMessage(MsgConstants.API_USER_ERROR));
        }
    }
    /**
     * 도메인이 동일한 업체 찾기
     * @param issueForm IssueForm
     * @return IssueForm
     */
    private IssueForm findCompanyField(IssueForm issueForm) {
        if(issueForm.getIssueCustomFields() != null && issueForm.getIssueCustomFields().size() > 0) {
            CompanyFieldCondition condition = new CompanyFieldCondition();
            List<Map<String, Object>> companyFields = this.companyFieldService.find(condition);
            List<Map<String, Object>> companyFields = Lists.newArrayList();
            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("도메인")) {
                Long customFieldId = MapUtil.getLong(issueCustomField, "customFieldId");
                CustomField customField = this.customFieldService.getCustomField(customFieldId);
                if(customField != null && customField.getCustomFieldType().equals(SITE) && customField.getName().equals("도메인")) {
                    String useValue = issueCustomField.get("useValue").toString();
                    String[] urlArr = null;
                    List<String> urls = Lists.newArrayList();
                    if (useValue.contains(",")) {
                        urlArr = useValue.split(",");
                        urls.addAll(Arrays.asList(urlArr));
                    } else {
                        urls.add(useValue);
                    }
                    condition.setUrl(urls);
                    companyFields = this.companyFieldService.find(condition);
                    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);
                                    }
                            companyField.put("companyId", companyField.get("id"));
                            issueCompanyFields.add(companyField);
                            if(companyFieldVo.getIspId() != null && companyFieldVo.getIspId() != -1) {
                                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);
                                    }
                            }
                            if(companyFieldVo.getHostingId() != null && companyFieldVo.getHostingId() != -1) {
                                Map<String, Object> hostingField = this.hostingFieldService.find(companyFieldVo.getHostingId());
                                if (hostingField != null) {
                                    hostingField.put("hostingId", hostingField.get("id"));
                                    issueHostingFields.add(hostingField);
                                }
                            }
                        }
@@ -362,7 +394,43 @@
                issueForm.setIssueHostingFields(issueHostingFields);
            }
        }
        return issueForm;
    }
    /**
     * 조건에 맞는 파트너 정보 찾기
     * @param condition CompanyFieldCondition
     * @param issueCompanyFields List<Map<String, Object>>
     * @param issueIspFields List<Map<String, Object>>
     * @param issueHostingFields List<Map<String, Object>>
     */
    private void findPartner(CompanyFieldCondition condition, List<Map<String, Object>> issueCompanyFields
            , List<Map<String, Object>> issueIspFields, List<Map<String, Object>> issueHostingFields) {
        List<Map<String, Object>> companyFields = this.companyFieldService.find(condition);
        if(companyFields != null && companyFields.size() > 0) {
            for (Map<String, Object> companyField : companyFields) {
                CompanyFieldVo companyFieldVo = ConvertUtil.convertMapToClass(companyField, CompanyFieldVo.class);
                companyField.put("companyId", companyField.get("id"));
                issueCompanyFields.add(companyField);
                if(companyFieldVo.getIspId() != null && companyFieldVo.getIspId() != -1) {
                    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 && companyFieldVo.getHostingId() != -1) {
                    Map<String, Object> hostingField = this.hostingFieldService.find(companyFieldVo.getHostingId());
                    if (hostingField != null) {
                        hostingField.put("hostingId", hostingField.get("id"));
                        issueHostingFields.add(hostingField);
                    }
                }
            }
        }
    }
    private User convertToUser(String token) {
@@ -391,10 +459,12 @@
            // 가상 상위 이슈 추가
            parentIssueForm.setUseIssueCustomFields(issueApiForm.getUseIssueCustomFieldIds());
            //  같은 도메인 업체 찾기
            IssueForm partners = this.findCompanyField(parentIssueForm);
            parentIssueForm.setIssueCompanyFields(partners.getIssueCompanyFields());
            parentIssueForm.setIssueIspFields(partners.getIssueIspFields());
            parentIssueForm.setIssueHostingFields(partners.getIssueHostingFields());
            if (parentIssueForm.getIssueCompanyFields() == null) {
                IssueForm partners = this.findCompanyField(parentIssueForm);
                parentIssueForm.setIssueCompanyFields(partners.getIssueCompanyFields());
                parentIssueForm.setIssueIspFields(partners.getIssueIspFields());
                parentIssueForm.setIssueHostingFields(partners.getIssueHostingFields());
            }
            Issue issue = addIssue(user, parentIssueForm, null);
            issues.add(issue);
@@ -416,23 +486,62 @@
    }
    // 중복된 상위 이슈 검색
    private List<Issue> findIssue(IssueApiForm issueApiform, List<CustomFieldApiOverlap> customFieldApiOverlaps, Long userId) {
    private List<Issue> findIssue(IssueApiForm issueApiform, IssueForm issueForm, List<CustomFieldApiOverlap> customFieldApiOverlaps, Long userId) {
        List<IssueCustomFieldValueForm> issueCustomFieldValueForms = issueApiform.getIssueCustomFieldValues();
        List<Issue> resultIssueVos = Lists.newArrayList();
        String comma = ",";
        if (issueCustomFieldValueForms.size() > 0) {
            String concatUseValue = "";
            String customFieldType = "";
            int useIdx = 0;
            int cntIp = 0;
            int cntSite = 0;
            IssueCustomFieldValueFormComparator comp = new IssueCustomFieldValueFormComparator();
            Collections.sort(issueCustomFieldValueForms, comp);
            List<String> userValues = Lists.newArrayList();
            CompanyFieldCondition condition = new CompanyFieldCondition();
            List<Map<String, Object>> issueCompanyFields = Lists.newArrayList();
            List<Map<String, Object>> issueIspFields = Lists.newArrayList();
            List<Map<String, Object>> issueHostingFields = Lists.newArrayList();
            for (IssueCustomFieldValueForm issueCustomFieldValueForm : issueCustomFieldValueForms) {
                userValues.add(issueCustomFieldValueForm.getUseValue());
                for(CustomFieldApiOverlap customFieldApiOverlap : customFieldApiOverlaps) {
                    if (customFieldApiOverlap.getCustomField().getId().equals(issueCustomFieldValueForm.getCustomFieldId())) {
                        String useValue = issueCustomFieldValueForm.getUseValue();
                        if (useValue.contains(" ")) {
                            useValue = useValue.replace(" ","");
                        }
                        if (customFieldApiOverlap.getCustomField().getCustomFieldType().equals(IP_ADDRESS)) {
                            long ip = ConvertUtil.ipToLong(useValue);
                            customFieldType = IP_ADDRESS.toString();
                            if (cntIp == 0){
                                condition.setIp(ip);
                            }
                            cntIp ++;
                        }
                        if(customFieldApiOverlap.getCustomField().getCustomFieldType().equals(SITE)) {
                            String[] urlArr = null;
                            List<String> urls = Lists.newArrayList();
                            if (useValue.contains(",")) {
                                urlArr = useValue.split(",");
                                urls.addAll(Arrays.asList(urlArr));
                            } else {
                                urls.add(useValue);
                            }
                            if (cntSite == 0) {
                                condition.setUrl(urls);
                            }
                            cntSite ++;
                        }
                        if (useIdx > 0) {
                            concatUseValue = concatUseValue.concat(comma);
                        }
@@ -442,11 +551,29 @@
                }
            }
            // 추가 할 url or ip에 포함되어있는 파트너 찾기
            this.findPartner(condition, issueCompanyFields, issueIspFields, issueHostingFields);
            issueForm.setIssueCompanyFields(issueCompanyFields);
            issueForm.setIssueIspFields(issueIspFields);
            issueForm.setIssueHostingFields(issueHostingFields);
            IssueCustomFieldValueCondition issueCustomFieldValueCondition = new IssueCustomFieldValueCondition();
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            issueCustomFieldValueCondition.setUseValues(userValues);
            issueCustomFieldValueCondition.setIssueTypeId(issueApiform.getIssueTypeId());
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            issueCustomFieldValueCondition.setCustomFieldType(customFieldType);
//            issueCustomFieldValueCondition.setIssueStatusType("CLOSE");
            List<Map<String, Object>> results = Lists.newArrayList();
            if (customFieldType.equals(IP_ADDRESS.toString()) && issueForm.getIssueCompanyFields() != null && issueForm.getIssueCompanyFields().size() > 0) {
                long ipValue = ConvertUtil.ipToLong(concatUseValue);
                issueCustomFieldValueCondition.setUseValue(String.valueOf(ipValue));
                //  하위이슈조건의 타입이 IP일 경우 같은 업체정보인 이슈 찾기
                results = this.issueMapper.findByCustomFieldValueOfIp(issueCustomFieldValueCondition);
            } else {
                results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            }
            if (results != null && results.size() > 0) {
                for (Map<String, Object> result : results) {
                    resultIssueVos.add(this.getIssue(MapUtil.getLong(result, "id")));
@@ -468,6 +595,8 @@
    @Override
    @Transactional
    public Issue addIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        StringBuilder detectIssueChange = new StringBuilder();
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId());
        //  프로젝트 유효성 체크
@@ -498,6 +627,12 @@
        if (issueForm.getParentIssueId() != null){
            Issue parentIssue = this.getIssue(issueForm.getParentIssueId());
            issue.setParentIssue(parentIssue);
            // 상위 이슈가 종료일경우 대기로 변경
            IssueStatus parentIssueStatus = parentIssue.getIssueStatus();
            if (parentIssueStatus.getIssueStatusType().equals(IssueStatusType.CLOSE)) {
                parentIssue.setIssueStatus(issueStatus);
            }
        }
        issue.setIssueNumber(this.issueNumberGeneratorService.generateIssueNumber(project));    //  각 프로젝트의 고유 이슈 번호 생성
@@ -505,16 +640,26 @@
        issue = this.issueRepository.saveAndFlush(issue);
        issue.setReverseIndex(issue.getId() * -1);  //  쿼리 속도 개선을 위해 리버스 인덱스 생성
        if (issueForm.getParentIssueId() != null){
            Issue parentIssue = this.getIssue(issueForm.getParentIssueId());
            if (issueForm.getIsApi().equals(Issue.IS_API_YES)
                    || (issueForm.getInheritYn() != null && issueForm.getInheritYn())) {
                //  하위이슈에 상위이슈의 파트너 정보 적용
                this.inheritPartners(issue, parentIssue);
            }
        }
        //  담당자 지정
        //this.issueUserService.modifyIssueUser(issue, project.getWorkspace(), issueForm.getUserIds());
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm, detectIssueChange);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        this.issueIspService.modifyIssueIspField(issue, issueForm, detectIssueChange);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        this.issueHostingService.modifyIssueHostingField(issue, issueForm, detectIssueChange);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -552,15 +697,17 @@
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(IssueForm issueForm, List<MultipartFile> multipartFiles) {
    public Issue addDownIssue(Map<String, Object> resJsonData, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        User user = this.webAppUtil.getLoginUserObject();
        return addDownIssue(user, issueForm, multipartFiles);
        return addDownIssue(resJsonData, user, issueForm, multipartFiles);
    }
    //  하위이슈를 생성한다.
    @Override
    @Transactional
    public Issue addDownIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
    public Issue addDownIssue(Map<String, Object> resJsonData, User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        StringBuilder detectIssueChange = new StringBuilder();
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId());
        //  프로젝트 유효성 체크
@@ -603,11 +750,11 @@
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm, detectIssueChange);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        this.issueIspService.modifyIssueIspField(issue, issueForm, detectIssueChange);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        this.issueHostingService.modifyIssueHostingField(issue, issueForm, detectIssueChange);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -622,6 +769,7 @@
        this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.ADD, null);
        //  이슈 위험 관리 생성
        this.issueRiskService.addIssueRisk(issue, project.getWorkspace());
        //  영속성 컨텍스트 비우기
        this.clear();
        //  이슈 생성, 삭제시 예약 이메일에 등록해놓는다.
@@ -631,13 +779,29 @@
        UserVo userVo = ConvertUtil.copyProperties(user, UserVo.class);
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(userVo, ElasticSearchConstants.ISSUE_ADD));
        IssueVo issueVo = this.convertToIssueVo(issue);
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo);
        return issue;
    }
    /**
     * Issue를 IssueVo로 변환(하위이슈의 파트너 정보 상속 시 필요)
     * @param issue Issue
     * @return IssueVo
     */
    private IssueVo convertToIssueVo(Issue issue) {
        IssueVo issueVo = ConvertUtil.copyProperties(issue, IssueVo.class);
        issueVo.setInheritPartners(issue.getIssueType().getInheritPartners());
        issueVo.setUsePartner(issue.getIssueType().getUsePartner());
        return issueVo;
    }
    //  연관이슈를 생성한다.
    @Override
    @Transactional
    public Issue addRelIssue(User user, IssueForm issueForm, List<MultipartFile> multipartFiles) {
        StringBuilder detectIssueChange = new StringBuilder();
        //  사용하고 있는 업무 공간이 활성 상태인지 확인한다. 사용 공간에서 로그인한 사용자가 비활성인지 확인한다.
        Workspace workspace = this.workspaceService.checkUseWorkspace(user, user.getLastWorkspaceId());
        //  프로젝트 유효성 체크
@@ -680,11 +844,11 @@
        //  담당부서 지정
        this.issueDepartmentService.modifyIssueDepartment(issue, user, project.getWorkspace(), issueForm.getDepartmentIds());
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm, detectIssueChange);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        this.issueIspService.modifyIssueIspField(issue, issueForm, detectIssueChange);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        this.issueHostingService.modifyIssueHostingField(issue, issueForm, detectIssueChange);
        //  첨부 파일 저장
        //  multipartFile 을 file Map List 객체로 변경한다.
@@ -786,7 +950,7 @@
                useValues.add(issueCustomFieldValueVo.getUseValue());
                customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), useValues);
            } else {
                if (issueCustomFieldValueVo.getCustomFieldVo().getCustomFieldType().equals(CustomFieldType.INPUT.toString())) {
                if (issueCustomFieldValueVo.getCustomFieldVo().getCustomFieldType().equals(INPUT.toString())) {
                    customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), issueCustomFieldValueVo.getUseValue());
                } else {
                    customField.put(issueCustomFieldValueVo.getCustomFieldVo().getName(), Lists.newArrayList(issueCustomFieldValueVo.getUseValue()));
@@ -840,15 +1004,28 @@
        }
    }
    //  날짜 유효성 체크
    /**
     * 날짜 유효성 체크
     * @param startDate 시작 일자(문자)
     * @param completeDate 종료 일자(문자)
     */
    private void checkStartCompleteDate(String startDate, String completeDate) {
        if (!StringUtils.isEmpty(startDate) && !StringUtils.isEmpty(completeDate)) {
            Date start = DateUtil.convertStrToDate(startDate, "yy-MM-dd");
            Date end = DateUtil.convertStrToDate(completeDate, "yy-MM-dd");
            if (start.getTime() > end.getTime()) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.DATE_PICKER_NOT_AVAILABLE));
            }
            checkStartCompleteDate(start, end);
        }
    }
    /**
     * 날짜 유효성 체크
     * @param start 시작 일자
     * @param end 종료 일자
     */
    private void checkStartCompleteDate(Date start, Date end) {
        if (start.getTime() > end.getTime()) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.DATE_PICKER_NOT_AVAILABLE));
        }
    }
@@ -924,42 +1101,40 @@
            return Lists.newArrayList();
        }
        //  튜닝 전 - 1.3 / 1.2 / 1.1
        //  튜닝 후 (단일/다중 검색 조건 3개 기준) - 0.49 / 0.41 / 0.47 / 0.41
        List<IssueVo> issueVos = Lists.newArrayList();  //  이슈 목록 데이터 저장 컬렉션
        //  사용자 정의 필드로 검색한 이슈 아이디 값
        List<String> issueKeys = Lists.newArrayList(issueIds);
        issueCondition.setIssueIds(issueKeys);
        issueCondition.setLoginUserId(this.webAppUtil.getLoginId());
        issueCondition.setWorkspaceId(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());
        User user = this.webAppUtil.getLoginUserObject();
        issueCondition.setLoginUserId(user.getId());
        issueCondition.setWorkspaceId(user.getLastWorkspaceId());
        List<Map<String, Object>> results = Lists.newArrayList();
        Long totalCount = 0L;
        UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
//        UserLevel userLevel = this.userLevelService.getUserLevel(user.getUserLevel().getId());
//        if (!this.userWorkspaceService.checkWorkspaceManager(user)
//                && !MngPermission.checkMngPermission(userLevel.getPermission(), MngPermission.USER_PERMISSION_MNG_ISSUE)) { //최고관리자 & 프로젝트,이슈 관리자 일 경우 모든 이슈 보기
//            this.SetMyDepartmentId(issueCondition);
            //this.SetAllDepartmentId(issueCondition);
        //this.SetAllDepartmentId(issueCondition);
//        } /*else{
//            results = this.issueMapper.findByDepartment(issueCondition);
//            totalCount = this.issueMapper.countByDepartment(issueCondition);
//        }*/
//        StopWatch serviceStart = new StopWatch();
//        serviceStart.start();
        results = this.issueMapper.find(issueCondition);
//         serviceStart.stop();
//        log.error("result : " + serviceStart.toString());
//        serviceStart = new StopWatch();
//        serviceStart.start();
        totalCount = this.issueMapper.count(issueCondition);
        //  튜닝 전 - 0.8, 0.9, 0.9, 0.9, 0.9
        /*StopWatch serviceStart = new StopWatch();
        serviceStart.start();*/
        //  튜닝 전 - 1.1, 1.1, 1.3, 1.2
        /*serviceStart.stop();
        log.debug("serviceENd1 : " + serviceStart.getTime());*/
//        serviceStart.stop();
//        log.error("totalCount : " + serviceStart.toString());
        int totalPage = (int) Math.ceil((totalCount - 1) / pageable.getPageSize()) + 1;
        //  이슈 아이디 초기화
@@ -975,8 +1150,8 @@
            this.setDownIssues(user, issueVos);
            this.setRelationIssues(issueVos);
        }
        this.setCountDownIssues(issueVos);
        this.setCountDownIssues(issueVos);
        this.SetWorkflowDepartment(issueVos); //워크플로우에 설정한 담당부서 가져오기
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVos);
@@ -1231,6 +1406,9 @@
    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);
            if (MapUtil.getString(result, "inheritPartners") != null && MapUtil.getString(result, "inheritPartners").equals("1")) {
                issueVo.setInheritPartners(true);
            }
            issueVos.add(issueVo);
            issueCondition.addIssueIds(String.valueOf(issueVo.getId()));
        }
@@ -1471,19 +1649,26 @@
    //  이슈 상세 정보를 조회한다.
    @Override
    @Transactional(readOnly = true)
    public void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition, Pageable relPageable, Pageable downPageable) {
    public void detailIssue(Map<String, Object> resJsonData, IssueCondition issueCondition) {
        IssueVo issueVo = new IssueVo();
        Pageable relPageable = issueCondition.getRelPageable();
        Pageable downPageable = issueCondition.getDownPageable();
        if (issueCondition.getId() != null) {
            Issue issue = this.getIssue(issueCondition.getId());
            issueVo = ConvertUtil.copyProperties(issue, IssueVo.class);
            User user = this.webAppUtil.getLoginUserObject();
            issueVo.setRelPage(relPageable.getPageNumber() * relPageable.getPageSize());
            issueVo.setRelPageSize(relPageable.getPageSize());
            issueVo.setDownPage(downPageable.getPageNumber() * downPageable.getPageSize());
            issueVo.setDownPageSize(downPageable.getPageSize());
            if (relPageable != null) {
                issueVo.setRelPageNumber(relPageable.getPageNumber());
                issueVo.setRelPageSize(relPageable.getPageSize());
                issueVo.setRelPage(relPageable.getPageNumber() * relPageable.getPageSize());
            }
            if (downPageable != null) {
                issueVo.setDownPageNumber(downPageable.getPageNumber());
                issueVo.setDownPage(downPageable.getPageNumber() * downPageable.getPageSize());
                issueVo.setDownPageSize(downPageable.getPageSize());
            }
            switch (issueCondition.getDeep()) {
                case "01": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당부서, 첨부파일, 사용자 정의 필드 정보를 셋팅한다.
@@ -1504,29 +1689,21 @@
                case "02": //  프로젝트, 이슈 유형, 이슈 상태,  우선순위, 중요도, 담당자, 첨부파일, 사용자 정의 필드 정보, 댓글, 기록을 셋팅한다.
                    this.setIssueDetail(issueVo, issue, user);    //  이슈 상세 정보를 셋팅한다.
                    this.setIssueTableConfigs(issue, issueVo);
                    this.setIssueTableConfigs(issue, issueVo, issueCondition);
                    issueVo.setProjectVo(ConvertUtil.copyProperties(issue.getProject(), ProjectVo.class));
                    break;
            }
        }
        Long relTotalCount = issueVo.getRelTotalCount();
        int relTotalPage = issueVo.getRelTotalPage();
        Long downTotalCount = issueVo.getDownTotalCount();
        int downTotalPage = issueVo.getDownTotalPage();
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_DETAIL));
        resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo);
        resJsonData.put(Constants.REQ_KEY_RELATION_ISSUE_PAGE_VO, new ResPage(relPageable.getPageNumber(), relPageable.getPageSize(),
                relTotalPage, relTotalCount));
        resJsonData.put(Constants.REQ_KEY_DOWN_ISSUE_PAGE_VO, new ResPage(downPageable.getPageNumber(), downPageable.getPageSize(),
                downTotalPage, downTotalCount));
    }
    // 테이블 설정 셋팅
    private void setIssueTableConfigs(Issue issue, IssueVo issueVo) {
        Long IssueTypeId = issue.getIssueType().getId();
    private void setIssueTableConfigs(Issue issue, IssueVo issueVo, IssueCondition issueCondition) {
        //Long IssueTypeId = issue.getIssueType().getId();
        Long IssueTypeId = issueCondition.getIssueTypeId();
        for (int tableConfigType : IssueTableConfig.IssueTableTypes) {
            if (tableConfigType != IssueTableConfig.ISSUE_TABLE_TYPE_MAIN) {
@@ -1546,13 +1723,21 @@
    // 하위 이슈 정보를 셋팅한다
    private void setDownIssues(Issue issue, IssueVo issueVo) {
        //List<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId());
        int startPage = (int) Math.floor(issueVo.getDownPage()/issueVo.getDownPageSize());
        Pageable pageable = PageRequest.of(startPage, issueVo.getDownPageSize());
        Page<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId(), pageable);
        issueVo.setDownTotalPage(downIssues.getTotalPages());
        issueVo.setDownTotalCount(downIssues.getTotalElements());
        Page<Issue> downIssues = null;
        List<Issue> downIssueList = this.issueRepository.findByParentIssueId(issue.getId());
        if(downIssueList != null && downIssueList.size() > 0) {
            int startPage = 0;
            if (issueVo.getDownPage() != 0) {
                startPage = (int) Math.floor(issueVo.getDownPage()/issueVo.getDownPageSize());
            }
            Pageable pageable = PageRequest.of(startPage, issueVo.getDownPageSize());
            downIssues = this.issueRepository.findByParentIssueId(issue.getId(), pageable);
        }
        if(downIssues != null){
            issueVo.setDownTotalPage(downIssues.getTotalPages());
            issueVo.setDownTotalCount(downIssues.getTotalElements());
            List<IssueVo> resultList = new ArrayList<>();
            for(Issue downIssue : downIssues){
                IssueVo downIssueVo = ConvertUtil.copyProperties(downIssue, IssueVo.class);
@@ -1569,6 +1754,9 @@
                this.setIssueCustomFields(downIssue, downIssueVo);   // 사용자정의필드 정보 세팅
                this.setIssueHistory(downIssue, downIssueVo);   //  이슈 기록 정보 셋팅
                this.setIssueComments(downIssue, downIssueVo);  //  댓글 정보 셋팅
                this.setIssueCompanyField(downIssue, downIssueVo);  //업체 정보 세팅
                this.setIssueIspField(downIssue, downIssueVo);  //ISP 정보 세팅
                this.setIssueHostingField(downIssue, downIssueVo);  //HOSTING 정보 세팅
                downIssueVo.setModifyPermissionCheck(issueVo.getModifyPermissionCheck());
@@ -1582,6 +1770,11 @@
    @Override
    @Transactional(readOnly = true)
    public void setIssueDetail(IssueVo issueVo, Issue issue, User user) {
        //  이슈 수정 권한을 갖고 있는지 확인
        if (this.checkHasPermission(issueVo, issueVo.getUserVos(), user, issueVo.getDepartmentVos())) {
            issueVo.setModifyPermissionCheck(Boolean.TRUE);
        }
        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");
@@ -1617,11 +1810,6 @@
        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);
        }
    }
    //  상위일감 정보 추가
@@ -1653,17 +1841,17 @@
    // 연관 이슈 정보를 셋팅한다
    private void setRelationIssue(Issue issue, IssueVo issueVo) {
        //Set<IssueRelation> issueRelations = issue.getIssueRelations();
        List<Map<String, Object>> results = this.issueRelationMapper.findByIssueId(issueVo);
        Long totalCount = this.issueRelationMapper.count(issueVo);
        int totalPage = (int) Math.ceil((totalCount - 1) / issueVo.getRelPageSize()) + 1;
        issueVo.setRelTotalPage(totalPage);
        issueVo.setRelTotalCount(totalCount);
        if (issue != null && issueVo != null && results.size() > 0) {
            int totalPage = (int) Math.ceil((totalCount - 1) / issueVo.getRelPageSize()) + 1;
            issueVo.setRelTotalPage(totalPage);
            issueVo.setRelTotalCount(totalCount);
            for (Map<String, Object> result : results) {
                IssueRelationVo issueRelationVo = ConvertUtil.convertMapToClass(result, IssueRelationVo.class);
                Issue relationIssue = this.findOne(issueRelationVo.getId());
                Issue relationIssue = this.findOne(MapUtil.getLong(result, "relationIssueId"));
                IssueVo relIssueVo = ConvertUtil.copyProperties(relationIssue, IssueVo.class);
                Project project = this.projectService.getProject(relationIssue.getProject().getId());
                relIssueVo.setProjectId(project.getId());
@@ -1685,6 +1873,25 @@
                this.setRegister(relationIssue, relIssueVo); // 등록자
                this.setIssueDepartment(relationIssue, relIssueVo);  //  담당부서 정보 셋팅
                this.setIssueCustomFields(relationIssue, relIssueVo);   // 사용자정의필드 정보 세팅
                Set<IssueCompany> issueCompanies = relationIssue.getIssueCompanies();
                Iterator<IssueCompany> itrCompany = issueCompanies.iterator();
                while (itrCompany.hasNext()) {
                    issueRelationVo.addIssueCompanyVo(ConvertUtil.copyProperties(itrCompany.next(), IssueCompanyVo.class));
                }
                Set<IssueIsp> issueIsps = relationIssue.getIssueIspFields();
                Iterator<IssueIsp> itrIsp = issueIsps.iterator();
                while (itrIsp.hasNext()) {
                    issueRelationVo.addIssueIspVo(ConvertUtil.copyProperties(itrIsp.next(), IssueIspVo.class));
                }
                Set<IssueHosting> issueHostings = relationIssue.getIssueHostingFields();
                Iterator<IssueHosting> itrHosting = issueHostings.iterator();
                while (itrHosting.hasNext()) {
                    issueRelationVo.addIssueHostingVo(ConvertUtil.copyProperties(itrHosting.next(), IssueHostingVo.class));
                }
                issueVo.addIssueRelationVo(issueRelationVo);
            }
        } else {
@@ -1817,6 +2024,7 @@
            issueCustomFieldValueCondition.setUseValue(concatUseValue);
            issueCustomFieldValueCondition.setUseValues(userValues);
            issueCustomFieldValueCondition.setIssueTypeId(issueApiform.getIssueTypeId());
            issueCustomFieldValueCondition.setIssueStatusType("CLOSE");
            List<Map<String, Object>> results = this.issueMapper.findByCustomFieldValue(issueCustomFieldValueCondition);
            if (results != null && results.size() > 0) {
                for (Map<String, Object> result : results) {
@@ -2041,20 +2249,30 @@
        this.checkNotHaveIssueIdAttachedFile(issue, issueForm);
        //  사용자 정의 필드 저장
        this.issueCustomFieldValueService.modifyIssueCustomFieldValue(issue, issueForm.getIssueCustomFields());
        //  이슈 이력 등록
        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));
        //  업체 정보 저장
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm);
        this.issueCompanyService.modifyIssueCompanyField(issue, issueForm, detectIssueChange);
        //  ISP 정보 저장
        this.issueIspService.modifyIssueIspField(issue, issueForm);
        this.issueIspService.modifyIssueIspField(issue, issueForm, detectIssueChange);
        //  HOSTING 정보 저장
        this.issueHostingService.modifyIssueHostingField(issue, issueForm);
        this.issueHostingService.modifyIssueHostingField(issue, issueForm, detectIssueChange);
        //  파트너정보 하위이슈 상속
        List<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId());
        if (issueForm.getInheritYn() != null && issueForm.getInheritYn()
                && downIssues != null && downIssues.size() > 0) {
            for (Issue downIssue : downIssues) {
                this.inheritPartners(downIssue, issue);
            }
        }
        //  이슈 이력 등록
        if (!StringUtils.isEmpty(detectIssueChange.toString())) {
            this.issueHistoryService.addIssueHistory(issue, user, IssueHistoryType.MODIFY, detectIssueChange.toString());
        }
        return issue;
    }
@@ -2490,21 +2708,24 @@
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_REMOVE_NOT_SELECT));
        }
        List<Issue> removeIssues = Lists.newArrayList();
        Set<Long> removeIds = new HashSet<>();
        for (Long issueId : issueForm.getRemoveIds()) {
            removeIds.add(issueId);
            //하위이슈 체크
            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);
                    removeIds.add(downIssueId);
                }
            }
            Issue issue = this.issueRemoves(issueId, user);
            removeIssues.add(issue);
        }
        for (Long removeId : removeIds) {
            this.issueRemoves(removeId, user);
        }
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_REMOVE));
    }
@@ -2699,6 +2920,9 @@
        excelInfo.addAttrInfos(new ExportExcelAttrVo("register", this.messageAccessor.message("common.register"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 등록자
        excelInfo.addAttrInfos(new ExportExcelAttrVo("period", this.messageAccessor.message("common.period"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 기간
        excelInfo.addAttrInfos(new ExportExcelAttrVo("modifyDate", this.messageAccessor.message("common.modifyDate"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 최종 변경일
        excelInfo.addAttrInfos(new ExportExcelAttrVo("companyName", this.messageAccessor.message("common.company"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 업체
        excelInfo.addAttrInfos(new ExportExcelAttrVo("ispName", this.messageAccessor.message("common.isp"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // ISP
        excelInfo.addAttrInfos(new ExportExcelAttrVo("hostingName", this.messageAccessor.message("common.hosting"), 20, ExportExcelAttrVo.ALIGN_CENTER)); // 호스팅
        //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
@@ -2759,8 +2983,16 @@
        for (IssueVo issueVo : issueVos) {
            for (Map<String, Object> issueCustomFieldValue : issueCustomFieldValues) {
                int count = 0;
                Map<String, Object> useValues = new HashMap<>();
                if (issueVo.getId().equals(MapUtil.getLong(issueCustomFieldValue, "issueId"))) {
                    IssueCustomFieldValueVo issueCustomFieldValueVo = new IssueCustomFieldValueVo();
                    useValues.put("useValue"+count, MapUtil.getString(issueCustomFieldValue, "useValue"));
                    useValues.put("customFieldId", MapUtil.getLong(issueCustomFieldValue, "customFieldId"));
                    issueCustomFieldValueVo.setUseValues(useValues);
                    issueCustomFieldValueVo.setUseValue(MapUtil.getString(issueCustomFieldValue, "useValue"));
                    CustomFieldVo customFieldVo = new CustomFieldVo();
@@ -2782,6 +3014,30 @@
            CompanyField companyField = issueCompany.getCompanyField();
            if (companyField != null) {
                issueCompanyVo.setCompanyId(issueCompany.getCompanyField().getId());
                if (issueCompany.getCompanyTypeId() != null && issueCompany.getCompanyTypeId() != -1) {
                    CompanyFieldCategory companyType = this.companyFieldCategoryService.find(issueCompany.getCompanyTypeId());
                    issueCompanyVo.setCompanyTypeName(companyType.getUseValue());
                }
                if (issueCompany.getParentSectorId() != null && issueCompany.getParentSectorId() != -1) {
                    CompanyFieldCategory parentSector = this.companyFieldCategoryService.find(issueCompany.getParentSectorId());
                    issueCompanyVo.setParentSectorName(parentSector.getUseValue());
                }
                if (issueCompany.getChildSectorId() != null && issueCompany.getChildSectorId() != -1) {
                    CompanyFieldCategory childSector = this.companyFieldCategoryService.find(issueCompany.getChildSectorId());
                    issueCompanyVo.setChildSectorName(childSector.getUseValue());
                }
                if (issueCompany.getRegionId() != null && issueCompany.getRegionId() != -1) {
                    CompanyFieldCategory region = this.companyFieldCategoryService.find(issueCompany.getRegionId());
                    issueCompanyVo.setRegionName(region.getUseValue());
                }
                if (issueCompany.getStatusId() != null && issueCompany.getStatusId() != -1) {
                    if (issueCompany.getStatusName() != null && !issueCompany.getStatusName().equals("")) {
                        issueCompanyVo.setStatusName(issueCompany.getStatusName());
                    } else {
                        CompanyFieldCategory status = this.companyFieldCategoryService.find(issueCompany.getStatusId());
                        issueCompanyVo.setStatusName(status.getUseValue());
                    }
                }
            }
            issueCompanyVos.add(issueCompanyVo);
        }
@@ -2851,6 +3107,9 @@
                result.put("departments", CommonUtil.convertDepartmentVosToString(issueVo.getDepartmentVos()));
                result.put("priorityName", issueVo.getPriorityName());
                result.put("severityName", issueVo.getSeverityName());
                result.put("companyName", issueVo.getCompanyName());
                result.put("ispName", issueVo.getIspName());
                result.put("hostingName", issueVo.getHostingName());
                UserVo register = this.userService.removeSensitiveUser(issueVo.getRegisterId());
                //  등록자
@@ -2928,6 +3187,9 @@
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.importance"), 5, ExportExcelAttrVo.ALIGN_CENTER)); // 중요도
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.startDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 시작일
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.endDate"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 종료일
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.company"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 업체
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.isp"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // ISP
        excelInfo.addAttrInfos(new ExportExcelAttrVo("id", this.messageAccessor.message("common.hosting"), 10, ExportExcelAttrVo.ALIGN_CENTER)); // 호스팅
        //  프로젝트에 연결된 사용자 정의 필드 정보를 추출하여 엑셀 download 템플릿을 만든다.
        this.makeIssueExcelTemplateCustomFields(excelInfo, conditions);
        //  엑셀에 넣을 데이터 - IssueVos 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
@@ -2967,15 +3229,23 @@
            Map<Long, Long> issueNumberMaps = new HashMap<>();  //  이슈 번호 모음
            Map<String, Long> issueTypeCustomFieldMaps = new HashMap<>(); //  이슈 타입 + 사용자 정의 필드 연결 정보
            Map<String, CompanyField> companyFieldMaps = new HashMap<>();   //업체 모음
            Map<String, IspField> ispFieldMaps = new HashMap<>();   //isp 모음
            Map<String, HostingField> hostingFieldMaps = new HashMap<>();   //호스팅 모음
            Workspace workspace = this.workspaceService.getWorkspace(this.userService.getUser(this.webAppUtil.getLoginId()).getLastWorkspaceId());  //  이슈를 넣으려는 업무 공간
            //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
            this.IssueAttributeMapToList(priorityMaps, severityMaps, departmentMaps, customFieldMaps, issueTypeCustomFieldMaps);
            this.IssueAttributeMapToList(issueForm, priorityMaps, severityMaps, departmentMaps, customFieldMaps,
                    issueTypeCustomFieldMaps, companyFieldMaps, ispFieldMaps, hostingFieldMaps);
            //  0.237 - 0.230
            List<IssueForm> issueForms = Lists.newArrayList();
            List<String> headers = Lists.newArrayList();
            Workbook workbook;
            IssueType issueType = new IssueType();
            Workflow workflow = new Workflow();
            workbook = WorkbookFactory.create(multipartFile.getInputStream());
            Sheet sheet = workbook.getSheetAt(0);
@@ -3012,14 +3282,20 @@
                //  1번 헤더부터 데이터 영역
                if (rowIndex > 1) {
                    //  이슈로 등록하기 위해 IssueForm 에 데이터를 셋팅한다.
                    IssueForm newIssueForm = this.setIssueFormToExcelField(row, (rowIndex + 1), priorityMaps, severityMaps, departmentMaps, customFieldMaps, headers);
                    IssueForm newIssueForm = this.setIssueFormToExcelField(row, (rowIndex + 1), priorityMaps, severityMaps, customFieldMaps,
                            companyFieldMaps, ispFieldMaps, hostingFieldMaps, headers);
                    ConvertUtil.copyProperties(issueForm, newIssueForm);
                    Project project = this.projectService.getProject(newIssueForm.getProjectId());
                    Long issueNumber = this.issueNumberGeneratorService.generateIssueNumber(project);
                    newIssueForm.setIssueNumber(issueNumber);
                    issueType = this.issueTypeService.getIssueType(newIssueForm.getIssueTypeId());
                    workflow = issueType.getWorkflow();
                    IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow);
                    newIssueForm.setIssueStatusId(issueStatus.getId());
                    issueForms.add(newIssueForm);
                }
            }
@@ -3028,47 +3304,28 @@
            }
            //  1.176
            //  이슈 등록
//            this.issueMapper.insertBatch(issueForms);
            this.issueMapper.insertBatch(issueForms);
            for (IssueForm saveIssueForm : issueForms) {
                Issue issue = new Issue();
                ConvertUtil.copyProperties(saveIssueForm, issue);
                IssueType issueType = this.issueTypeService.getIssueType(saveIssueForm.getIssueTypeId());
                Workflow workflow = issueType.getWorkflow();
                Project project = this.projectService.getProject(saveIssueForm.getProjectId());
                Long issueNumber = this.issueNumberGeneratorService.generateIssueNumber(project);
                IssueStatus issueStatus = this.issueStatusService.findByIssueStatusTypeIsReady(workflow);
                issue.setPriority(this.priorityService.getPriority(saveIssueForm.getPriorityId()));
                issue.setSeverity(this.severityService.getSeverity(saveIssueForm.getSeverityId()));
                issue.setIssueStatus(issueStatus);
                issue.setIssueType(issueType);
                issue.setProject(project);
                issue.setIssueNumber(issueNumber);
                issue.setParentIssue(this.getIssue(saveIssueForm.getParentIssueId()));
                issue = this.issueRepository.saveAndFlush(issue);
                if (issueForm.getInheritYn() != null && issueForm.getInheritYn() && issueForm.getParentIssueId() != null) {
                    Issue parentIssue = this.getIssue(issueForm.getParentIssueId());
                    //  상위이슈의 파트너 정보 상속
                    this.inheritPartners(issue, parentIssue);
                }
                saveIssueForm.setId(issue.getId());
                IssueDepartment issueDepartment = new IssueDepartment();
                issueDepartment.setIssue(issue);
                issueDepartment.setWorkspace(workspace);
                //  워크플로우 대기 상태의 부서 추가
                List<Long> departmentsIds = this.workflowDepartmentService.findFirstDepartmentIds(workflow);
                for (Long departmentId : departmentsIds) {
                    issueDepartment.setDepartment(this.departmentService.getDepartment(departmentId));
                if (departmentsIds != null && departmentsIds.size() > 0) {
                    this.issueDepartmentService.add(departmentsIds, workspace, issue);
                }
                issue.addIssueDepartment(issueDepartment);
                saveIssueForm.setIssueStatusId(issueStatus.getId());
                this.setIssuePartners(saveIssueForm, issue);
            }
            //  0.416 - 0.439
@@ -3088,8 +3345,6 @@
            this.bulkInsertIssueCustomFieldValue(issueForms, issueTypeCustomFieldMaps);
            //  3.628 - 3.445
            // 업체,ISP,호스팅 추가
            /*serviceStart.stop();
            log.debug("2차 저장 시간 : " + serviceStart.getTime());*/
@@ -3101,6 +3356,66 @@
            //  증가된 이슈 번호를 업데이트 한다.
//            issueNumberMaps.put(issueForm.getProjectId(), issueForm.getProjectId());
//            this.issueNumberGeneratorService.updateIssueNumber(issueNumberMaps);
        }
    }
    /**
     * 엑셀로 입력한 파트너 정보 저장
     * @param issueForm IssueForm
     */
    private void setIssuePartners(IssueForm issueForm, Issue issue) {
        //issueCompany 등록
        if (issueForm.getIssueCompanyFields() != null && issueForm.getIssueCompanyFields().size() > 0) {
            for (Map<String, Object> issueCompanyMap : issueForm.getIssueCompanyFields()) {
                CompanyField companyField =  ConvertUtil.convertMapToClass(issueCompanyMap, CompanyField.class);
                IssueCompany issueCompany = ConvertUtil.convertMapToClass(issueCompanyMap, IssueCompany.class, "id", "registerDate", "modifyDate");
                issueCompany.setCompanyField(companyField);
                issueCompany.setIssue(issue);
                this.issueCompanyRepository.saveAndFlush(issueCompany);
                //  사용자가 ISP를 직접 입력하지 않았을 경우 업체에 등록되어있는 ISP 설정
                if (issueForm.getIssueIspFields() == null || issueForm.getIssueIspFields().size() < 1) {
                    //  업체의 ISP가 있는 경우 issueISP 등록
                    if (companyField.getIspId() != null && companyField.getIspId() != -1) {
                        IspField ispField = this.ispFieldService.getIsp(companyField.getIspId());
                        IssueIsp issueIsp = ConvertUtil.copyProperties(ispField, IssueIsp.class, "id", "registerDate", "modifyDate");
                        issueIsp.setIspField(ispField);
                        issueIsp.setIssue(issue);
                        this.issueIspRepository.saveAndFlush(issueIsp);
                    }
                }
                //  사용자가 호스팅을 직접 입력하지 않았을 경우 업체에 등록되어있는 호스팅 설정
                if (issueForm.getIssueHostingFields() == null || issueForm.getIssueHostingFields().size() < 1) {
                    //  업체의 호스팅이 있는 경우 issueHosting 등록
                    if (companyField.getHostingId() != null && companyField.getHostingId() != -1) {
                        HostingField hostingField = this.hostingFieldService.getHosting(companyField.getHostingId());
                        IssueHosting issueHosting = ConvertUtil.copyProperties(hostingField, IssueHosting.class, "id", "registerDate", "modifyDate");
                        issueHosting.setHostingField(hostingField);
                        issueHosting.setIssue(issue);
                        this.issueHostingRepository.saveAndFlush(issueHosting);
                    }
                }
            }
        }
        //issueIsp 등록
        if (issueForm.getIssueIspFields() != null && issueForm.getIssueIspFields().size() > 0) {
            for (Map<String, Object> issueIspMap : issueForm.getIssueIspFields()) {
                IssueIsp issueIsp = ConvertUtil.convertMapToClass(issueIspMap, IssueIsp.class, "id", "registerDate", "modifyDate");
                IspField ispField = ConvertUtil.convertMapToClass(issueIspMap, IspField.class);
                issueIsp.setIspField(ispField);
                issueIsp.setIssue(issue);
                this.issueIspRepository.saveAndFlush(issueIsp);
            }
        }
        //issueHosting 등록
        if (issueForm.getIssueHostingFields() != null && issueForm.getIssueHostingFields().size() > 0) {
            for (Map<String, Object> issueHostingMap : issueForm.getIssueHostingFields()) {
                IssueHosting issueHosting = ConvertUtil.convertMapToClass(issueHostingMap, IssueHosting.class, "id", "registerDate", "modifyDate");
                HostingField hostingField = ConvertUtil.convertMapToClass(issueHostingMap, HostingField.class);
                issueHosting.setHostingField(hostingField);
                issueHosting.setIssue(issue);
                this.issueHostingRepository.saveAndFlush(issueHosting);
            }
        }
    }
@@ -3201,6 +3516,9 @@
                issueCustomField.put("registerId", this.webAppUtil.getLoginId());
                issueCustomFieldValueMaps.add(issueCustomField);
            }
            //  엑셀에 업체명을 입력하지 않았을 경우 같은 도메인 업체 찾기
            this.findPartnerByDomain(issueForm);
        }
        if (issueCustomFieldValueMaps.size() > 0) {
@@ -3208,9 +3526,58 @@
        }
    }
    /**
     * 엑셀에 업체명을 입력하지 않았을 경우 같은 도메인 업체 찾기
     * @param issueForm IssueForm
     */
    private void findPartnerByDomain(IssueForm issueForm) {
        if (issueForm.getIssueCompanyFields() == null || issueForm.getIssueCompanyFields().size() < 1) {
            // 같은 도메인 업체 찾기
            IssueForm partners = this.findCompanyField(issueForm);
            Issue issue = this.findOne(issueForm.getId());
            if (partners.getIssueCompanyFields() != null && partners.getIssueCompanyFields().size() > 0) {
                for (Map<String, Object> company : partners.getIssueCompanyFields()) {
                    IssueCompany issueCompany = ConvertUtil.convertMapToClass(company, IssueCompany.class);
                    CompanyField companyField = ConvertUtil.convertMapToClass(company, CompanyField.class);
                    issueCompany.setCompanyField(companyField);
                    issueCompany.setIssue(issue);
                    this.issueCompanyRepository.saveAndFlush(issueCompany);
                }
            }
            if (partners.getIssueIspFields() != null && partners.getIssueIspFields().size() > 0) {
                for (Map<String, Object> isp : partners.getIssueIspFields()) {
                    IssueIsp issueIsp = ConvertUtil.convertMapToClass(isp, IssueIsp.class);
                    IspField ispField = ConvertUtil.convertMapToClass(isp, IspField.class);
                    issueIsp.setIspField(ispField);
                    issueIsp.setIssue(issue);
                    this.issueIspRepository.saveAndFlush(issueIsp);
                }
            }
            if (partners.getIssueHostingFields() != null && partners.getIssueHostingFields().size() > 0) {
                for (Map<String, Object> hosting : partners.getIssueHostingFields()) {
                    IssueHosting issueHosting = ConvertUtil.convertMapToClass(hosting, IssueHosting.class);
                    HostingField hostingField = ConvertUtil.convertMapToClass(hosting, HostingField.class);
                    issueHosting.setHostingField(hostingField);
                    issueHosting.setIssue(issue);
                    this.issueHostingRepository.saveAndFlush(issueHosting);
                }
            }
        }
    }
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    private void IssueAttributeMapToList(Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps,Map<String, Long> issueTypeCustomFieldMaps) {
    private void IssueAttributeMapToList(IssueForm issueForm, Map<String, Priority> priorityMaps, Map<String, Severity> severityMaps,
                                         Map<String, DepartmentVo> departmentMaps, Map<String, CustomField> customFieldMaps,Map<String, Long> issueTypeCustomFieldMaps,
                                         Map<String, CompanyField> companyFieldMaps, Map<String, IspField> ispFieldMaps, Map<String, HostingField> hostingFieldMaps) {
        Project project = this.projectService.getProject(issueForm.getProjectId());
        for (IssueTypeCustomField issueTypeCustomField : project.getIssueTypeCustomFields()) {
            //  빠르게 찾기 위해 이슈 타입 아이디 + 사용자 정의 필드 아이디를 키로 한다.
            String makeKey = issueTypeCustomField.getIssueType().getId().toString() + issueTypeCustomField.getCustomField().getId().toString();
            issueTypeCustomFieldMaps.put(makeKey, issueTypeCustomField.getId());
        }
        //  우선순위를 바로 찾을 수 있게 준비
        List<Priority> priorities = this.priorityService.findByWorkspaceId();
@@ -3229,43 +3596,112 @@
        for (CustomField customField : customFields) {
            customFieldMaps.put(customField.getName(), customField);
        }
        //  업체 정보를 바로 찾을 수 있게 준비
        List<CompanyField> companyFields = this.companyFieldService.findAll();
        for (CompanyField companyField : companyFields) {
            companyFieldMaps.put(companyField.getName(), companyField);
        }
        //  ISP 정보를 바로 찾을 수 있게 준비
        List<IspField> ispFields = this.ispFieldService.findAll();
        for (IspField ispField : ispFields) {
            ispFieldMaps.put(ispField.getName(), ispField);
        }
        //  호스팅 정보를 바로 찾을 수 있게 준비
        List<HostingField> hostingFields = this.hostingFieldService.findAll();
        for (HostingField hostingField : hostingFields) {
            hostingFieldMaps.put(hostingField.getName(), hostingField);
        }
    }
    /**
     * cell String으로 변환 함수
     * @param cell Cell
     * @param isNull boolean
     * @return String
     */
    private String stringToCell (Cell cell, boolean isNull) {
        String cellStr = "";
        if (!isNull) {
            cellStr = CommonUtil.convertExcelStringToCell(cell);
            //  공백 제거
            cell.setCellValue(cellStr.trim());
        } else {
            cell.setCellValue(cellStr);
        }
        return cellStr;
    }
    /**
     * cell NULL 체크 함수
     * 빈 값이 아닌 cell 체크
     * @param cell Cell
     * @return boolean
     */
    private Boolean cellNullCheck (Cell cell) {
        int cellType = cell.getCellType();
        if (cellType < Cell.CELL_TYPE_BLANK) {
            if (cellType == Cell.CELL_TYPE_STRING) {
                if (cell.getStringCellValue() != null && !cell.getStringCellValue().equals("")) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }
    //  엑셀 필드에 있는 정보를 이슈 form 으로 옮긴다.
    private IssueForm setIssueFormToExcelField(Row row, int rowIndex, Map<String, Priority> priorityMaps,
                                               Map<String, Severity> severityMaps, Map<String, DepartmentVo> departmentMaps,
                                               Map<String, CustomField> customFieldMaps, List<String> headers) {
                                               Map<String, Severity> severityMaps, Map<String, CustomField> customFieldMaps,
                                               Map<String, CompanyField> companyFieldMaps, Map<String, IspField> ispFieldMaps, Map<String, HostingField> hostingFieldMaps,
                                               List<String> headers) throws ParseException {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
        //  제목, 내용, 프로젝트 키, 이슈 타입, 우선순위, 중요도, 담당자, 시작일, 종료일, 사용자 정의 필드
        for (int cellIndex = 0; cellIndex < headers.size(); cellIndex++) {
            Cell cell = row.getCell(cellIndex);
            String cellStr = "";
            boolean isNull = true;
            if (cell != null) {
                isNull = cellNullCheck(cell);
                cellStr = stringToCell(cell, isNull); //cell을 String으로 변환
            }
            switch (cellIndex) {
                case 0:
                    //  이슈 제목을 IssueForm 에 저장한다.
                    this.setIssueFormTitle(cell, issueForm, rowIndex);
                    if (isNull) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TITLE_IS_NULL, rowIndex));
                    }
                    this.setIssueFormTitle(cellStr, issueForm, rowIndex);
                    break;
                case 1:    //  내용
                    if (cell != null) {
                        issueForm.setDescription(CommonUtil.convertExcelStringToCell(cell));
                    } else {
                        //  null 입력 방지
                        issueForm.setDescription("");
                    }
                    issueForm.setDescription(cellStr);
                    break;
                case 2:
                    //  우선순위를 IssueForm 에 저장한다.
                    this.setIssueFormPriority(cell, priorityMaps, issueForm, rowIndex);
                    if (isNull) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_IS_NULL, rowIndex));
                    }
                    this.setIssueFormPriority(cellStr, priorityMaps, issueForm, rowIndex);
                    break;
                case 3:
                    //  중요도를 IssueForm 에 저장한다.
                    this.setIssueFormSeverity(cell, severityMaps, issueForm, rowIndex);
                    if (isNull) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_IS_NULL, rowIndex));
                    }
                    this.setIssueFormSeverity(cellStr, severityMaps, issueForm, rowIndex);
                    break;
                /*case 6:
                    //  담당자를 IssueForm 에 저장한다.
@@ -3273,34 +3709,74 @@
                    break;*/
                case 4:
                    //  시작일을 IssueForm 에 저장한다.
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, true, rowIndex);
                    }
                    this.setIssueFormPeriod(cellStr, issueForm, true, rowIndex, isNull);
                    break;
                case 5:
                    //  종료일을 IssueForm 에 저장한다.
                    if (cell != null) {
                        this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    }
                    this.setIssueFormPeriod(cellStr, issueForm, false, rowIndex, isNull);
                    break;
                case 6:
                    //  업체를 IssueForm 에 저장한다.
                    this.setIssueFormCompanyField(cellStr, companyFieldMaps, issueForm, rowIndex);
                    break;
                case 7:
                    //  ISP를 IssueForm 에 저장한다.
                    this.setIssueFormIspField(cellStr, ispFieldMaps, issueForm, rowIndex);
                    break;
                case 8:
                    //  호스팅을 IssueForm 에 저장한다.
                    this.setIssueFormHostingField(cellStr, hostingFieldMaps, issueForm, rowIndex);
                    break;
                default:
                    //  9번 부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
                    this.setIssueFormCustomFieldValue(cellStr, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
            }
        }
        return issueForm;
    }
    //  이슈 제목을 IssueForm 에 저장한다.
    private void setIssueFormTitle(Cell cell, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISSUE_TITLE_IS_NULL, rowIndex));
    private void setIssueFormHostingField(String cell, Map<String, HostingField> hostingFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell.length() > 0) {
            Map<String, Object> issueHostingFields = new HashMap<>();
            HostingField hostingFieldMap = hostingFieldMaps.get(cell);
            if (hostingFieldMap == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HOSTING_NOT_EXIST, rowIndex));
            }
            ConvertUtil.copyProperties(hostingFieldMap, issueHostingFields);
            issueForm.addIssueHostingField(issueHostingFields);
        }
    }
        String title = CommonUtil.convertExcelStringToCell(cell);
    private void setIssueFormIspField(String cell, Map<String, IspField> ispFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell.length() > 0) {
            Map<String, Object> issueIspFields = new HashMap<>();
            IspField ispFieldMap = ispFieldMaps.get(cell);
            if (ispFieldMap == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_ISP_NOT_EXIST, rowIndex));
            }
            ConvertUtil.copyProperties(ispFieldMap, issueIspFields);
            issueForm.addIssueIspField(issueIspFields);
        }
    }
    private void setIssueFormCompanyField(String cell, Map<String, CompanyField> companyFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell.length() > 0) {
            Map<String, Object> issueCompanyFields = new HashMap<>();
            CompanyField companyFieldMap = companyFieldMaps.get(cell);
            if (companyFieldMap == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_COMPANY_NOT_EXIST, rowIndex));
            }
            ConvertUtil.copyProperties(companyFieldMap, issueCompanyFields);
            issueForm.addissueCompanyField(issueCompanyFields);
        }
    }
    //  이슈 제목을 IssueForm 에 저장한다.
    private void setIssueFormTitle(String title, IssueForm issueForm, int rowIndex) {
        //  제목 유효성 체크
        this.verifyTitle(title);
        issueForm.setTitle(title);
@@ -3341,13 +3817,8 @@
    //  우선순위를 IssueForm 에 저장한다.
    private void setIssueFormPriority(Cell cell, Map<String, Priority> priorityMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PRIORITY_IS_NULL, rowIndex));
        }
        Priority priority = priorityMaps.get(CommonUtil.convertExcelStringToCell(cell));
    private void setIssueFormPriority(String priorityStr, Map<String, Priority> priorityMaps, IssueForm issueForm, int rowIndex) {
        Priority priority = priorityMaps.get(priorityStr);
        if (priority == null) {
            throw new OwlRuntimeException(
@@ -3358,13 +3829,8 @@
    }
    //  중요도를 IssueForm 에 저장한다.
    private void setIssueFormSeverity(Cell cell, Map<String, Severity> severityMaps, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_SEVERITY_IS_NULL, rowIndex));
        }
        Severity severity = severityMaps.get(CommonUtil.convertExcelStringToCell(cell));
    private void setIssueFormSeverity(String strSeverity, Map<String, Severity> severityMaps, IssueForm issueForm, int rowIndex) {
        Severity severity = severityMaps.get(strSeverity);
        if (severity == null) {
            throw new OwlRuntimeException(
@@ -3390,20 +3856,11 @@
        }
    }
    //  시작일, 종료일을 IssueForm 에 저장한다.
    private void setIssueFormPeriod(Cell cell, IssueForm issueForm, Boolean checkStartDate, int rowIndex) {
        if (cell != null && !cell.toString().equals("")) {
    private void setIssueFormPeriod(String periodDate, IssueForm issueForm, Boolean checkStartDate, int rowIndex, boolean isNull) throws ParseException {
        if (!isNull) {
            //  값이 공백이면 중지
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
            Date startDate;
            try {
                startDate = cell.getDateCellValue();
            } catch (Exception e) {
            Date startDate = DateUtil.convertStrToDateOnly(periodDate);
            if (startDate == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY, rowIndex));
            }
@@ -3413,96 +3870,121 @@
            } else {
                issueForm.setCompleteDate(DateUtil.convertDateToStr(startDate, "yyyy-MM-dd"));
                //  종료일만 입력 했을 경우
                if (issueForm.getCompleteDate() != null && issueForm.getStartDate() == null) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY_EMPTY_START, rowIndex));
                }
                try {
                    //  날짜 유효성 체크
                    this.checkStartCompleteDate(issueForm.getStartDate(), issueForm.getCompleteDate());
                } catch (OwlRuntimeException e) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALIDITY, rowIndex));
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_PERIOD_NOT_VALID, rowIndex));
                }
            }
        }
    }
    //  사용자 정의 필드 정보를 IssueForm 에 저장한다.-
    private void setIssueFormCustomFieldValue(Cell cell, Map<String, CustomField> customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) {
        if (cell != null) {
            String cellValue = CommonUtil.convertExcelStringToCell(cell);
            Map<String, Object> issueCustomFieldMap = new HashMap<>();
            CustomField customField = customFieldMaps.get(customFieldName);
    private void setIssueFormCustomFieldValue(String cellValue, Map<String, CustomField> customFieldMaps, IssueForm issueForm, String customFieldName, int rowIndex) {
        Map<String, Object> issueCustomFieldMap = new HashMap<>();
        CustomField customField = customFieldMaps.get(customFieldName);
            if (customField == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST, rowIndex));
            }
            //  사용자 정의 필드 값이 공백이면 중지
            if (StringUtils.isEmpty(cellValue)) {
                return;
            }
        if (customField == null) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HEADER_CUSTOM_FIELD_NOT_EXIST, rowIndex));
        }
        //  사용자 정의 필드 값이 공백이면 중지
        if (StringUtils.isEmpty(cellValue)) {
            return;
        }
            boolean validity = false;
        boolean validity = false;
            switch (customField.getCustomFieldType()) {
                case INPUT:
                case NUMBER:
                case DATETIME:
                case IP_ADDRESS:
                case EMAIL:
                case SITE:
                case TEL:
                    if (cellValue.length() > 100) {
        switch (customField.getCustomFieldType()) {
            case INPUT:
            case NUMBER:
            case DATETIME:
            case IP_ADDRESS:
            case EMAIL:
            case SITE:
            case TEL:
                if (customField.getCustomFieldType() != INPUT && cellValue.length() > 100) { //INPUT 타입은 100자 제한 없음
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT));
                }
                //DATETIME일 경우 format 변경
                if (customField.getCustomFieldType() == DATETIME) {
                    Date date = DateUtil.convertStrToDate(cellValue);
                    if (date == null) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.CUSTOM_FIELD_TEXT_TYPE_MAX_LENGTH_OUT));
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_DATETIME_NOT_DASH, rowIndex));
                    }
                }
                    issueCustomFieldMap.put("customFieldId", customField.getId());
                    issueCustomFieldMap.put("useValue", cellValue);
                    issueForm.addIssueCustomFields(issueCustomFieldMap);
                    break;
                case SINGLE_SELECT:
                    //  값 유효성 체크
                //IP_ADDRESS일 경우 정규표현식 체크
                if (customField.getCustomFieldType() == IP_ADDRESS) {
                    String regExp = "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
                            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
                            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
                            + "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
                    if (!cellValue.matches(regExp)) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_IP_ADDRESS_NOT_VALIDITY, rowIndex));
                    }
                }
                issueCustomFieldMap.put("customFieldId", customField.getId());
                issueCustomFieldMap.put("useValue", cellValue);
                issueForm.addIssueCustomFields(issueCustomFieldMap);
                break;
            case SINGLE_SELECT:
                //  값 유효성 체크
                for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                    if (customFieldValue.getValue().equals(cellValue)) {
                        validity = true;
                        break;
                    }
                }
                if (!validity) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                }
                issueCustomFieldMap.put("customFieldId", customField.getId());
                issueCustomFieldMap.put("useValue", cellValue);
                issueForm.addIssueCustomFields(issueCustomFieldMap);
                break;
            case MULTI_SELECT:
                //  값 유효성 체크
                String[] useValues = cellValue.split("#");
                //  해, 달
                for (String useValue : useValues) {
                    for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                        if (customFieldValue.getValue().equals(cellValue)) {
                        if (customFieldValue.getValue().equals(useValue)) {
                            validity = true;
                            break;
                            Map<String, Object> multiValueMap = new HashMap<>();
                            multiValueMap.put("customFieldId", customField.getId());
                            multiValueMap.put("useValue", useValue);
                            issueForm.addIssueCustomFields(multiValueMap);
                        }
                    }
                }
                    if (!validity) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                    }
                if (!validity) {
                    throw new OwlRuntimeException(
                            this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                }
                    issueCustomFieldMap.put("customFieldId", customField.getId());
                    issueCustomFieldMap.put("useValue", cellValue);
                    issueForm.addIssueCustomFields(issueCustomFieldMap);
                    break;
                case MULTI_SELECT:
                    //  값 유효성 체크
                    String[] useValues = cellValue.split("#");
                    //  해, 달
                    for (String useValue : useValues) {
                        for (CustomFieldValue customFieldValue : customField.getCustomFieldValues()) {
                            if (customFieldValue.getValue().equals(useValue)) {
                                validity = true;
                                Map<String, Object> multiValueMap = new HashMap<>();
                                multiValueMap.put("customFieldId", customField.getId());
                                multiValueMap.put("useValue", useValue);
                                issueForm.addIssueCustomFields(multiValueMap);
                            }
                        }
                    }
                    if (!validity) {
                        throw new OwlRuntimeException(
                                this.messageAccessor.getMessage(MsgConstants.EXCEL_CUSTOM_FIELD_VALUE_NOT_VALIDITY, rowIndex));
                    }
                    break;
            }
                break;
        }
    }
@@ -3560,7 +4042,7 @@
    //  이슈를 템플릿에 따라 파트너 담당자에게 메일로 발송한다.
    @Override
    @Transactional(readOnly = true)
    public void sendIssueEmailPartners(EmailTemplateForm emailTemplateForm) {
    public void sendIssueEmailPartners(EmailTemplateForm emailTemplateForm, List<MultipartFile> multipartFiles) {
        if (emailTemplateForm.getSendEmails().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER));
@@ -3587,10 +4069,43 @@
        for(int i=0; i < sendMails.length; i++) {
            sendMails[i] = CommonUtil.decryptAES128(sendMails[i]);
        }
        this.systemEmailService.sendEmail(emailTemplateForm.getTitle(), emailTemplateForm.getTemplate(), sendMails, null);
        this.systemEmailService.sendEmail(emailTemplateForm.getTitle(), emailTemplateForm.getTemplate(), sendMails, null, multipartFiles);
        this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, emailTemplateForm.getSendEmails(), sb);
        this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.SEND, sb.toString());
    }
    @Override
    public void sendCommonEmail(EmailCommonForm emailCommonForm, List<MultipartFile> multipartFiles) {
        if (emailCommonForm.getSendEmails().size() < 1) {
            throw new OwlRuntimeException(
                    this.messageAccessor.getMessage(MsgConstants.ISSUE_NOT_SEND_USER));
        }
        Issue issue = null;
        if(emailCommonForm.getIssueId() != null) {
            issue = this.getIssue(emailCommonForm.getIssueId());
        }
        //  발신자 표시
        User user = this.webAppUtil.getLoginUserObject();
        UserVo toUser = this.webAppUtil.getLoginUser();
        //  사용자 시스템 기능 사용 정보 수집
        log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_ANOTHER_USER_SEND_EMAIL));
        StringBuilder sb = new StringBuilder();
        Locale locale = CommonUtil.getUserLanguage(user.getLanguage());
        String[] sendMails = ConvertUtil.ToArray(emailCommonForm.getSendEmails());
        for(int i=0; i < sendMails.length; i++) {
            sendMails[i] = CommonUtil.decryptAES128(sendMails[i]);
        }
        this.systemEmailService.sendEmail(emailCommonForm.getTitle(), emailCommonForm.getDescription(), sendMails, null, multipartFiles);
        if (issue != null) {
            this.issueHistoryService.detectSendIssueMail(IssueHistoryType.SEND, emailCommonForm.getSendEmails(), sb);
            this.issueHistoryService.addIssueHistory(issue, IssueHistoryType.SEND, sb.toString());
        }
    }
    //  예약 발생 이슈를 실행한다
@@ -3783,25 +4298,94 @@
                this.issueHistoryService.detectDownIssues(IssueHistoryType.DELETE, issue, sb);
                issue.setParentIssue(null);
            }
            if (issueDownForm.getInheritYn() != null && issueDownForm.getInheritYn() && issue.getParentIssue() != null) {
                //  상위이슈의 파트너 정보 상속받기
                issue = this.inheritPartners(issue, parentIssue);
            }
            this.issueHistoryService.addIssueHistory(parentIssue, IssueHistoryType.MODIFY, sb.toString()); //parentIssue = myIssue(기록은 현재 상세페이지에 해야하니까)
            this.issueRepository.saveAndFlush(issue);
        }
    }
    /**
     * 상위이슈의 파트너 정보 상속받기
     * @param issue Issue
     * @param parentIssue Issue
     * @return Issue
     */
    private Issue inheritPartners(Issue issue, Issue parentIssue) {
        if (parentIssue != null) {
            if (parentIssue.getIssueType().getInheritPartners()) {
                IssueCompany issueCompany = new IssueCompany();
                IssueIsp issueIsp = new IssueIsp();
                IssueHosting issueHosting = new IssueHosting();
                if (parentIssue.getIssueCompanies() != null && parentIssue.getIssueCompanies().size() > 0) {
                    issue.getIssueCompanies().clear();
                    issue.getIssueCompanies().addAll(parentIssue.getIssueCompanies());
                    Iterator<IssueCompany> itrCompany = issue.getIssueCompanies().iterator();
                    ConvertUtil.copyProperties(itrCompany.next(), issueCompany, "id");
                    issueCompany.setIssue(issue);
                    issueCompany.setCompanyField(parentIssue.getIssueCompanies().iterator().next().getCompanyField());
                    this.issueCompanyRepository.saveAndFlush(issueCompany);
                } else {
                    this.issueCompanyRepository.deleteByIssueId(issue.getId());
                    this.issueCompanyRepository.flush();
                }
                if (parentIssue.getIssueIspFields() != null && parentIssue.getIssueIspFields().size() > 0) {
                    issue.getIssueIspFields().clear();
                    issue.getIssueIspFields().addAll(parentIssue.getIssueIspFields());
                    Iterator<IssueIsp> itrIsp = issue.getIssueIspFields().iterator();
                    ConvertUtil.copyProperties(itrIsp.next(), issueIsp, "id");
                    issueIsp.setIssue(issue);
                    issueIsp.setIspField(parentIssue.getIssueIspFields().iterator().next().getIspField());
                    this.issueIspRepository.saveAndFlush(issueIsp);
                } else {
                    this.issueIspRepository.deleteByIssueId(issue.getId());
                    this.issueIspRepository.flush();
                }
                if (parentIssue.getIssueHostingFields() != null && parentIssue.getIssueHostingFields().size() > 0) {
                    issue.getIssueHostingFields().clear();
                    issue.getIssueHostingFields().addAll(parentIssue.getIssueHostingFields());
                    Iterator<IssueHosting> itrHosting = issue.getIssueHostingFields().iterator();
                    ConvertUtil.copyProperties(itrHosting.next(), issueHosting, "id");
                    issueHosting.setIssue(issue);
                    issueHosting.setHostingField(parentIssue.getIssueHostingFields().iterator().next().getHostingField());
                    this.issueHostingRepository.saveAndFlush(issueHosting);
                } else {
                    this.issueHostingRepository.deleteByIssueId(issue.getId());
                    this.issueHostingRepository.flush();
                }
            }
        }
        return issue;
    }
    @Override
    public void findPartner(Map<String, Object> resJsonData, Map<String, Object> params) {
        Long issueTypeId = MapUtil.getLong(params, "issueTypeId");
        IssueType issueType = this.issueTypeService.getIssueType(issueTypeId); // 이슈의 이슈유형 객체
        Integer using = issueType.getUsePartner() != null ? issueType.getUsePartner().intValue() : 0; // 이슈유형별로 사용중인 업체/ISP/호스팅 값
        List<UsePartnerVo> usePartnerVos = Lists.newArrayList();
        Integer using = 0;
        if (issueTypeId != null) {
            IssueType issueType = this.issueTypeService.getIssueType(issueTypeId); // 이슈의 이슈유형 객체
            using = issueType.getUsePartner() != null ? issueType.getUsePartner().intValue() : 0; // 이슈유형별로 사용중인 업체/ISP/호스팅 값
        } else {
            for (int partner : UsePartner.partners) {
                using += partner;
            }
        }
        for (Integer usePartner : UsePartner.partners) { //1(업체), 2(ISP), 4(호스팅)
            UsePartnerVo usePartnerVo = UsePartner.checkUsePartner(using, usePartner);
            if (usePartnerVo != null) {
                usePartnerVos.add(usePartnerVo);
            }
            resJsonData.put(Constants.RES_KEY_CONTENTS, usePartnerVos);
        }
        resJsonData.put(Constants.RES_KEY_CONTENTS, usePartnerVos);
    }
    @Override