OWL ITS + 탐지시스템(인터넷 진흥원)
- 파트너명 특수문자 입력 가능하도록 수정
- 이슈 import/export 시 파트너 정보 추가
- 이슈상세 - 메일 기록 추가
16개 파일 변경됨
354 ■■■■ 파일 변경됨
src/main/java/kr/wisestone/owl/constant/MsgConstants.java 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/domain/IssueCompany.java 8 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/CompanyFieldService.java 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/CompanyFieldServiceImpl.java 7 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java 219 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/WEB-INF/i18n/code_ko_KR.properties 3 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/WEB-INF/i18n/messages_ko_KR.properties 1 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/i18n/ko/global.json 2 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/scripts/app/issue/issueDetail.controller.js 27 ●●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/companyField/companyFieldAdd.html 4 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/companyField/companyFieldModify.html 3 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/hostingField/hostingFieldAdd.html 3 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/hostingField/hostingFieldModify.html 3 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/ispField/ispFieldAdd.html 3 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/ispField/ispFieldModify.html 3 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/webapp/views/issue/issueDetail.html 65 ●●●● 패치 | 보기 | raw | blame | 히스토리
src/main/java/kr/wisestone/owl/constant/MsgConstants.java
@@ -183,6 +183,7 @@
    public static final String EXCEL_IMPORT_COMPANY_NAME_IS_NULL = "EXCEL_IMPORT_COMPANY_NAME_IS_NULL";   //  다음 엑셀 라인에서 업체명이 입력지 않았습니다.
    public static final String EXCEL_IMPORT_URL_IS_NULL = "EXCEL_IMPORT_URL_IS_NULL";   //  다음 엑셀 라인에서 URL이 입력지 않았습니다.
    public static final String EXCEL_IMPORT_COMPANY_NOT_EXIST = "EXCEL_IMPORT_COMPANY_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 업체명으로 검색되는 업체가 없습니다.
    public static final String EXCEL_IMPORT_ISP_NOT_EXIST = "EXCEL_IMPORT_ISP_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 ISP명으로 검색되는 ISP가 없습니다.
    public static final String EXCEL_IMPORT_HOSTING_NOT_EXIST = "EXCEL_IMPORT_HOSTING_NOT_EXIST";   //  다음 엑셀 라인에서 입력된 호스팅명으로 검색되는 호스팅이 없습니다.
    public static final String EXCEL_IMPORT_PARENT_SECTOR_NOT_EQUAL = "EXCEL_IMPORT_PARENT_SECTOR_NOT_EQUAL";   //  다음 엑셀 라인에서 입력된 업종(중분류)은 업종(대분류)에 속해있지 않습니다.
src/main/java/kr/wisestone/owl/domain/IssueCompany.java
@@ -25,6 +25,7 @@
    private Long childSectorId;
    private Long regionId;
    private Long statusId;
    private String statusName;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "issue_id")
@@ -159,4 +160,11 @@
        this.statusId = statusId;
    }
    public String getStatusName() {
        return statusName;
    }
    public void setStatusName(String statusName) {
        this.statusName = statusName;
    }
}
src/main/java/kr/wisestone/owl/service/CompanyFieldService.java
@@ -40,4 +40,6 @@
    void importExcel(MultipartFile multipartFile) throws Exception;
    CompanyFieldVo CreateCompanyFieldCategory(CompanyFieldVo companyFieldVo, CompanyField companyField);
    List<CompanyField> findAll();
}
src/main/java/kr/wisestone/owl/service/impl/CompanyFieldServiceImpl.java
@@ -142,6 +142,13 @@
        return this.companyFieldMapper.find(condition);
    }
    //  모든 업체정보를 조회한다. 이슈 엑셀 import 에서 사용
    @Override
    @Transactional(readOnly = true)
    public List<CompanyField> findAll() {
        return this.companyFieldRepository.findAll();
    }
    /**
     * companyFieldCategory Name 설정
     * @param companyFieldVo CompanyFieldVo
src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -2755,6 +2755,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)); // 호스팅
        //  사용자 정의 필드를 사용한 이슈를 찾는다. 만약 이슈가 없다면 여기서 이슈 조회가 끝난다.
@@ -2855,8 +2858,12 @@
                    issueCompanyVo.setRegionName(region.getUseValue());
                }
                if (issueCompany.getStatusId() != null && issueCompany.getStatusId() != -1) {
                    CompanyFieldCategory status = this.companyFieldCategoryService.find(issueCompany.getStatusId());
                    issueCompanyVo.setStatusName(status.getUseValue());
                    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);
@@ -2927,6 +2934,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());
                //  등록자
@@ -3004,6 +3014,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 데이터를 엑셀에서 표시할 수 있는 데이터로 변경한다.
@@ -3043,9 +3056,14 @@
            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(issueForm, 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();
@@ -3088,14 +3106,11 @@
                //  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, departmentMaps, customFieldMaps,
                            companyFieldMaps, ispFieldMaps, hostingFieldMaps, headers);
                    ConvertUtil.copyProperties(issueForm, newIssueForm);
                    issueForms.add(newIssueForm);
                }
            }
@@ -3147,6 +3162,8 @@
                }
                saveIssueForm.setIssueStatusId(issueStatus.getId());
                this.setIssuePartners(saveIssueForm, issue);
            }
@@ -3160,7 +3177,7 @@
            //  이슈 담당자 벌크 등록
            this.bulkInsertIssueAssignee(issueForms, workspace);
            //  0.361 - 0.705
            //  1.816
            /*StopWatch serviceStart = new StopWatch();
            serviceStart.start();*/
@@ -3179,6 +3196,60 @@
            //  증가된 이슈 번호를 업데이트 한다.
//            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);
                issueCompany.setCompanyField(companyField);
                issueCompany.setIssue(issue);
                this.issueCompanyRepository.saveAndFlush(issueCompany);
                //  업체의 ISP가 있는 경우 issueISP 등록
                if (companyField.getIspId() != null) {
                    IspField ispField = this.ispFieldService.getIsp(companyField.getIspId());
                    IssueIsp issueIsp = ConvertUtil.copyProperties(ispField, IssueIsp.class);
                    issueIsp.setIspField(ispField);
                    issueIsp.setIssue(issue);
                    this.issueIspRepository.saveAndFlush(issueIsp);
                }
                //  업체의 호스팅이 있는 경우 issueHosting 등록
                if (companyField.getHostingId() != null) {
                    HostingField hostingField = this.hostingFieldService.getHosting(companyField.getHostingId());
                    IssueHosting issueHosting = ConvertUtil.copyProperties(hostingField, IssueHosting.class);
                    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);
                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);
                HostingField hostingField = ConvertUtil.convertMapToClass(issueHostingMap, HostingField.class);
                issueHosting.setHostingField(hostingField);
                issueHosting.setIssue(issue);
                this.issueHostingRepository.saveAndFlush(issueHosting);
            }
        }
    }
@@ -3279,27 +3350,39 @@
                issueCustomField.put("registerId", this.webAppUtil.getLoginId());
                issueCustomFieldValueMaps.add(issueCustomField);
            }
            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);
                    issueCompany.setIssue(issue);
                    this.issueCompanyRepository.saveAndFlush(issueCompany);
            //  엑셀에 업체명을 입력하지 않았을 경우 같은 도메인 업체 찾기
            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);
                    issueIsp.setIssue(issue);
                    this.issueIspRepository.saveAndFlush(issueIsp);
                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);
                    issueHosting.setIssue(issue);
                    this.issueHostingRepository.saveAndFlush(issueHosting);
                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);
                    }
                }
            }
        }
@@ -3311,7 +3394,8 @@
    //  이슈의 주요 속성을 map 에 저장하여 엑셀 import 에서 지정한 대상(이슈 속성)을 빠르게 찾을 수 있게 한다.
    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, 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());
@@ -3338,6 +3422,22 @@
        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);
        }
    }
    /**
@@ -3352,7 +3452,9 @@
    //  엑셀 필드에 있는 정보를 이슈 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, CustomField> customFieldMaps, Map<String, CompanyField> companyFieldMaps,
                                               Map<String, IspField> ispFieldMaps, Map<String, HostingField> hostingFieldMaps,
                                               List<String> headers) {
        IssueForm issueForm = new IssueForm();
        issueForm.setRegisterId(this.webAppUtil.getLoginId());
        Project project = null;
@@ -3401,6 +3503,24 @@
                        this.setIssueFormPeriod(cell, issueForm, false, rowIndex);
                    }
                    break;
                case 6:
                    //  업체를 IssueForm 에 저장한다.
                    if (cellNullCheck(cell)) {
                        this.setIssueFormCompanyField(cell, companyFieldMaps, issueForm, rowIndex);
                    }
                    break;
                case 7:
                    //  ISP를 IssueForm 에 저장한다.
                    if (cellNullCheck(cell)) {
                        this.setIssueFormIspField(cell, ispFieldMaps, issueForm, rowIndex);
                    }
                    break;
                case 8:
                    //  호스팅을 IssueForm 에 저장한다.
                    if (cellNullCheck(cell)) {
                        this.setIssueFormHostingField(cell, hostingFieldMaps, issueForm, rowIndex);
                    }
                    break;
                default:
                    //  9번 부터는 사용자 정의 필드. 사용자 정의 필드 정보를 IssueForm 에 저장한다.
                    this.setIssueFormCustomFieldValue(cell, customFieldMaps, issueForm, headers.get(cellIndex), rowIndex);
@@ -3410,6 +3530,45 @@
        return issueForm;
    }
    private void setIssueFormHostingField(Cell cell, Map<String, HostingField> hostingFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell != null) {
            Map<String, Object> issueHostingFields = new HashMap<>();
            HostingField hostingFieldMap = hostingFieldMaps.get(CommonUtil.convertExcelStringToCell(cell));
            if (hostingFieldMap == null) {
                throw new OwlRuntimeException(
                        this.messageAccessor.getMessage(MsgConstants.EXCEL_IMPORT_HOSTING_NOT_EXIST, rowIndex));
            }
            ConvertUtil.copyProperties(hostingFieldMap, issueHostingFields);
            issueForm.addIssueHostingField(issueHostingFields);
        }
    }
    private void setIssueFormIspField(Cell cell, Map<String, IspField> ispFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell != null) {
            Map<String, Object> issueIspFields = new HashMap<>();
            IspField ispFieldMap = ispFieldMaps.get(CommonUtil.convertExcelStringToCell(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(Cell cell, Map<String, CompanyField> companyFieldMaps, IssueForm issueForm, int rowIndex) {
        if (cell != null) {
            Map<String, Object> issueCompanyFields = new HashMap<>();
            CompanyField companyFieldMap = companyFieldMaps.get(CommonUtil.convertExcelStringToCell(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(Cell cell, IssueForm issueForm, int rowIndex) {
        if (cell == null) {
src/main/webapp/WEB-INF/i18n/code_ko_KR.properties
@@ -26,6 +26,9 @@
common.projectKey=\uD504\uB85C\uC81D\uD2B8 \uD0A4
common.startDate=\uC2DC\uC791\uC77C
common.endDate=\uC885\uB8CC\uC77C
common.company=\uC5C5\uCCB4\uBA85
common.isp=ISP\uBA85
common.hosting=\uD638\uC2A4\uD305\uBA85
common.registerDate=\uB4F1\uB85D\uC77C
common.admin=\uAD00\uB9AC\uC790
common.teamMember=\uD300\uC6D0
src/main/webapp/WEB-INF/i18n/messages_ko_KR.properties
@@ -187,6 +187,7 @@
EXCEL_IMPORT_COMPANY_NAME_IS_NULL=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C \uC5C5\uCCB4\uBA85\uC774 \uC785\uB825\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
EXCEL_IMPORT_URL_IS_NULL=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C URL\uC774 \uC785\uB825\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
EXCEL_IMPORT_COMPANY_NOT_EXIST=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C \uC785\uB825\uB41C \uC5C5\uCCB4\uBA85\uC73C\uB85C \uAC80\uC0C9\uB418\uB294 \uC5C5\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
EXCEL_IMPORT_ISP_NOT_EXIST=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C \uC785\uB825\uB41C ISP\uBA85\uC73C\uB85C \uAC80\uC0C9\uB418\uB294 ISP\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
EXCEL_IMPORT_HOSTING_NOT_EXIST=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C \uC785\uB825\uB41C \uD638\uC2A4\uD305\uBA85\uC73C\uB85C \uAC80\uC0C9\uB418\uB294 \uD638\uC2A4\uD305\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
EXCEL_IMPORT_PARENT_SECTOR_NOT_EQUAL=\uB2E4\uC74C \uC5D1\uC140 \uB77C\uC778\uC5D0\uC11C \uC785\uB825\uB41C \uC5C5\uC885(\uC911\uBD84\uB958)\uC740 \uC5C5\uC885(\uB300\uBD84\uB958)\uC5D0 \uC18D\uD574\uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \n \uB77C\uC778 \uC815\uBCF4 : {0}
src/main/webapp/i18n/ko/global.json
@@ -771,9 +771,11 @@
        "noDate": "기간 없음",
        "fullView": "전체보기",
        "comments": "댓글",
        "mails": "메일",
        "record": "기록",
        "recordDown": "하위이슈 기록",
        "downComments": "하위이슈 댓글",
        "downMails": "하위이슈 메일",
        "noAssignee": "담당자가 없습니다.",
        "noContent": "내용이 없습니다.",
        "noRecord": "기록이 없습니다.",
src/main/webapp/scripts/app/issue/issueDetail.controller.js
@@ -1248,19 +1248,46 @@
                function makeActiveHistory() {
                    $scope.vm.viewer.activeParentHistory = "";
                    $scope.vm.viewer.activeHistory = "";
                    $scope.vm.viewer.activeParentMailHistory = "";
                    var oriMailHistory = [];
                    if ($scope.vm.viewer.issueHistoryVos != null && $scope.vm.viewer.issueHistoryVos.length > 0) {
                        for (var i=0; i<$scope.vm.viewer.issueHistoryVos.length; i++) {
                            if ($scope.vm.viewer.issueHistoryVos[i].issueHistoryType === "SEND") {
                                oriMailHistory.push($scope.vm.viewer.issueHistoryVos[i]);
                            }
                        }
                        $scope.vm.viewer.activeParentMailHistory = angular.copy(oriMailHistory);
                    }
                    if ($scope.vm.viewer.issueDownVos != null && $scope.vm.viewer.issueDownVos.length > 0) {
                        $scope.vm.viewer.activeDownHistory = ""; //하위이슈 히스토리만
                        $scope.vm.viewer.downComment = ""; //하위이슈 댓글만
                        $scope.vm.viewer.activeDownMailHistory = ""; //하위이슈 메일만
                        $scope.vm.viewer.activeDownAllHistory = ""; //하위이슈 히스토리+댓글
                        var downTitle = "";
                        var oriDownHistory = [];
                        var oriDownCommentHistory = [];
                        var oriDownMailHistory = [];
                        // 하위이슈 히스토리
                        for (var i=0; i<$scope.vm.viewer.issueDownVos.length; i++){
                            oriDownHistory.push($scope.vm.viewer.issueDownVos[i].issueHistoryVos);
                            oriDownCommentHistory.push($scope.vm.viewer.issueDownVos[i].issueCommentVos);
                            //  하위이슈 메일 기록
                            for (var j=0; j<$scope.vm.viewer.issueDownVos[i].issueHistoryVos.length; j++) {
                                if($scope.vm.viewer.issueDownVos[i].issueHistoryVos[j].issueHistoryType === "SEND") {
                                    oriDownMailHistory.push($scope.vm.viewer.issueDownVos[i].issueHistoryVos[j]);
                                }
                            }
                        }
                        //  하위이슈 메일만 저장
                        $scope.vm.viewer.activeDownMailHistory = angular.copy(oriDownMailHistory);
                        $scope.vm.viewer.activeDownMailHistory.sort(function (a, b) {   //내림차순
                            return a.registerDate > b.registerDate ? -1 : a.registerDate < b.registerDate ? 1 : 0;
                        });
                        for (var i=0; i<oriDownHistory.length; i++){
                            if ($scope.vm.viewer.activeDownHistory === "") {
                                $scope.vm.viewer.activeDownHistory = oriDownHistory[i];
src/main/webapp/views/companyField/companyFieldAdd.html
@@ -21,13 +21,13 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       input-regex="/[^?a-zA-Z0-9/]/"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="companyField.enterSpecialCharacters">업체 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="companyField.enterSpecialCharacters">업체 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
src/main/webapp/views/companyField/companyFieldModify.html
@@ -21,13 +21,12 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="companyField.enterSpecialCharacters">업체 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="companyField.enterSpecialCharacters">업체 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
                <label for="companyFieldAddForm10" class="issue-label">
src/main/webapp/views/hostingField/hostingFieldAdd.html
@@ -21,13 +21,12 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="hostingField.enterSpecialCharacters">호스팅 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="hostingField.enterSpecialCharacters">호스팅 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
                <label for="hostingFieldAddForm10">
src/main/webapp/views/hostingField/hostingFieldModify.html
@@ -21,13 +21,12 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="hostingField.enterSpecialCharacters">호스팅 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="hostingField.enterSpecialCharacters">호스팅 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
                <label for="hostingFieldModifyForm10">
src/main/webapp/views/ispField/ispFieldAdd.html
@@ -21,13 +21,12 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="ispField.enterSpecialCharacters">ISP 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="ispField.enterSpecialCharacters">ISP 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
                <label for="ispFieldAddForm10">
src/main/webapp/views/ispField/ispFieldModify.html
@@ -21,13 +21,12 @@
                       class="form-control"
                       autofocus
                       kr-input
                       input-regex="[^a-zA-Z0-9 가-힣ㄱ-ㅎㅏ-ㅣ\u318D\u119E\u11A2\u2022\u2025a\u00B7\uFE55]"
                       autocomplete="off"
                       ng-model="vm.form.name"
                       ng-maxlength="100"
                       maxlength="100"
                       required>
                <small translate="ispField.enterSpecialCharacters">ISP 이름에는 특수 문자를 입력 할수 없습니다.</small>
                <!--<small translate="ispField.enterSpecialCharacters">ISP 이름에는 특수 문자를 입력 할수 없습니다.</small>-->
            </div>
            <div class="form-group">
                <label for="ispFieldModifyForm10">
src/main/webapp/views/issue/issueDetail.html
@@ -827,22 +827,30 @@
                                           ng-click="fn.updateActiveHistory()"><span
                                                translate="common.fullView">전체보기</span></a>
                                    </li>
                                    <!--<li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'REPLY' }"
                                           ng-click="vm.activeTab = 'REPLY'"><span translate="common.comments">댓글</span></a>
                                    </li>-->
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'RECODE' }"
                                           ng-click="vm.activeTab = 'RECODE'"><span translate="common.record">기록</span></a>
                                    </li>
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'MAIL' }"
                                           ng-click="vm.activeTab = 'MAIL'"><span translate="common.mails">메일</span></a>
                                    </li>
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'REPLY' }"
                                           ng-click="vm.activeTab = 'REPLY'"><span translate="common.comments">댓글</span></a>
                                    </li>
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'RECODE_DOWN' }"
                                           ng-click="vm.activeTab = 'RECODE_DOWN'"><span translate="common.recordDown">하위이슈 기록</span></a>
                                    </li>
                                    <!--<li class="nav-item cursor">
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'MAIL_DOWN' }"
                                           ng-click="vm.activeTab = 'MAIL_DOWN'"><span translate="common.downMails">하위이슈 메일</span></a>
                                    </li>
                                    <li class="nav-item cursor">
                                        <a class="nav-link" ng-class="{ 'active' : vm.activeTab == 'REPLY_DOWN' }"
                                           ng-click="vm.activeTab = 'REPLY_DOWN'"><span translate="common.downComments">하위이슈 댓글</span></a>
                                    </li>-->
                                    </li>
                                </ul>
                            </div>
@@ -983,6 +991,20 @@
                                        </div>
                                    </div>
                                </div>
                                <!--    메일 보기    -->
                                <div ng-if="vm.activeTab == 'MAIL'">
                                    <div class="no-cont2" ng-if="vm.viewer.activeParentMailHistory.length < 1" translate="common.noRecord">
                                        기록이 없습니다.
                                    </div>
                                    <div class="ae-item" ng-repeat="issueHistory in vm.viewer.activeParentMailHistory">
                                        <div class="aei-content" ng-if="$root.isDefined(issueHistory.issueHistoryType)">
                                            <div dom-append dom="issueHistory.description"></div>
                                        </div>
                                    </div>
                                </div>
                                <!--    하위이슈 기록 보기    -->
                                <div ng-if="vm.activeTab == 'RECODE_DOWN'">
                                    <div class="no-cont2" ng-if="!$root.isDefined(vm.viewer.activeDownAllHistory) || vm.viewer.activeDownAllHistory.length < 1" translate="common.noRecord">
@@ -1027,28 +1049,39 @@
                                    </div>
                                </div>
                                <!--    하위이슈 메일 보기    -->
                                <div ng-if="vm.activeTab == 'MAIL_DOWN'">
                                    <div class="no-cont2" ng-if="vm.viewer.activeDownMailHistory.length < 1" translate="common.noRecord">
                                        기록이 없습니다.
                                    </div>
                                    <div class="ae-item" ng-repeat="issueHistory in vm.viewer.activeDownMailHistory">
                                        <div class="aei-content" ng-if="$root.isDefined(issueHistory.issueHistoryType)">
                                            <div dom-append dom="issueHistory.description"></div>
                                        </div>
                                    </div>
                                </div>
                                <!--    하위이슈 댓글 보기    -->
                                <div ng-if="vm.activeTab == 'REPLY_DOWN'">
                                    <div class="no-cont2" ng-if="$scope.vm.viewer.downComment.length < 1"
                                         translate="common.noComments">
                                        댓글이 없습니다.
                                    </div>
                                    <div class="ae-item" ng-repeat="comment in vm.viewer.downComment">
                                        <div class="re-title">
                                            <div class='dot'>
                                                하위이슈: {{::comment.title}}
                                            </div>
                                    <div class="aei-content pb-3" ng-repeat="comment in vm.viewer.downComment">
                                        <div class='dot mt-10 mb-10'>
                                            *하위이슈: {{::comment.title}}
                                        </div>
                                        <div class="aei-image">
                                        <div class="aei-image" style="position: absolute; top: 30%">
                                            <div class="user-avatar-w">
                                                <img alt="image" ng-src="{{::comment.profile}}">
                                                <img style="margin-top: 15px" alt="image" ng-src="{{::comment.profile}}">
                                            </div>
                                        </div>
                                        <div class="aei-content">
                                            <h6 class="aei-title">
                                            <h6 class="aei-title mt-1 ml-3per">
                                                {{::comment.registerName}}
                                            </h6>
                                            <div class="date-break">
                                            <div class="date-break mt-2" style="margin-left: 3.3%">
                                                <span>{{::comment.registerDate}}</span>
                                            </div>
                                            <div class="aei-delete"
@@ -1057,7 +1090,7 @@
                                                <i class="os-icon os-icon-close"></i>
                                            </div>
                                            <div class="chat-messages">
                                                <div class="message">
                                                <div class="message" style="margin-left: 3.3%">
                                                    <div class="message-content">
                                                        <span class="issue-detail-word-break">{{::comment.description}}</span>
                                                    </div>