src/main/java/kr/wisestone/owl/domain/Issue.java
@@ -101,6 +101,10 @@ @JoinColumn(name = "workflow_status_id") private WorkflowStatus workflowStatus; @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name = "parent_issue_id") private Issue parentIssue; public Long getId() { return id; } @@ -321,5 +325,11 @@ this.issueRelations.clear(); } public Issue getParentIssue() { return parentIssue; } public void setParentIssue(Issue parentIssue) { this.parentIssue = parentIssue; } } src/main/java/kr/wisestone/owl/repository/IssueRepository.java
@@ -2,6 +2,10 @@ import kr.wisestone.owl.domain.Issue; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; import java.util.List; public interface IssueRepository extends JpaRepository<Issue, Long> { List<Issue> findByParentIssueId(@Param("parentIssueId") Long parentIssueId); } src/main/java/kr/wisestone/owl/service/IssueService.java
@@ -68,4 +68,6 @@ void reservationIssue(); Map<String, Object> findTask(IssueCondition taskCondition); void modifyParentIssue(IssueForm issueForm); } src/main/java/kr/wisestone/owl/service/impl/IssueServiceImpl.java
@@ -799,6 +799,7 @@ this.setAttachedFiles(issue, issueVo); // 첨부 파일 정보 셋팅 this.setIssueCustomFields(issue, issueVo); // 사용자 정의 필드 값 정보 셋팅 this.setRelationIssue(issue, issueVo); //연관 일감 셋팅 this.setDownIssues(issue, issueVo); break; @@ -812,6 +813,13 @@ log.info(ElasticSearchUtil.makeUserActiveHistoryMessage(this.webAppUtil.getLoginUser(), ElasticSearchConstants.ISSUE_DETAIL)); resJsonData.put(Constants.RES_KEY_CONTENTS, issueVo); } // 하위 이슈 정보를 셋팅한다 private void setDownIssues(Issue issue, IssueVo issueVo) { List<Issue> downIssues = this.issueRepository.findByParentIssueId(issue.getId()); List<IssueVo> issueVos = ConvertUtil.convertObjectsToClasses(downIssues, IssueVo.class); issueVo.setIssueDownVos(issueVos); } // 이슈 상세 정보를 셋팅한다. @@ -833,6 +841,7 @@ this.setIssueComments(issue, issueVo); // 댓글 정보 셋팅 this.setIssueHistory(issue, issueVo); // 이슈 기록 정보 셋팅 this.setRelationIssue(issue, issueVo); //연관 일감 셋팅 this.setDownIssues(issue, issueVo); } // 등록자 정보 추가 @@ -2513,4 +2522,19 @@ return tasks; } @Transactional @Override public void modifyParentIssue(IssueForm issueForm) { Issue issue = this.issueRepository.getOne(issueForm.getId()); Long parentIssueId = issueForm.getParentIssueId(); if (parentIssueId != null) { Issue parentIssue = this.issueRepository.getOne(parentIssueId); issue.setParentIssue(parentIssue); } else { issue.setParentIssue(null); } this.issueRepository.saveAndFlush(issue); } } src/main/java/kr/wisestone/owl/vo/IssueVo.java
@@ -44,11 +44,13 @@ private List<IssueTypeCustomFieldVo> issueTypeCustomFieldVos = Lists.newArrayList(); private List<IssueCustomFieldValueVo> issueCustomFieldValueVos = Lists.newArrayList(); private List<IssueRelationVo> issueRelations = Lists.newArrayList(); private List<IssueVo> issueDownVos = Lists.newArrayList(); private List<IssueVo> issueRelationVos = Lists.newArrayList(); private Long attachedFileCount; private Long issueCommentCount; private String modifyByName; // 변경자 정보 - 이슈 변경 정보 상세 확인에서 사용 private WorkflowStatusVo workflowStatusVo; private Long parentIssueId; // 상위 일감 public IssueVo(){} @@ -379,4 +381,22 @@ public void setDepartmentVos(List<DepartmentVo> departmentVos) { this.departmentVos = departmentVos; } public Long getParentIssueId() { return parentIssueId; } public void setParentIssueId(Long parentIssueId) { this.parentIssueId = parentIssueId; } public List<IssueVo> getIssueDownVos() { return issueDownVos; } public void setIssueDownVos(List<IssueVo> issueDownVos) { this.issueDownVos = issueDownVos; } } src/main/java/kr/wisestone/owl/web/condition/IssueCondition.java
@@ -33,6 +33,7 @@ private Long workspaceId; private String projectType; private String deep; private Long parentIssueId; // 상위 일감 private List<Long> projectIds = Lists.newArrayList(); private List<Long> issueStatusIds = Lists.newArrayList(); private List<Long> issueTypeIds = Lists.newArrayList(); @@ -406,4 +407,11 @@ this.excludeIds = excludeIds; } public Long getParentIssueId() { return parentIssueId; } public void setParentIssueId(Long parentIssueId) { this.parentIssueId = parentIssueId; } } src/main/java/kr/wisestone/owl/web/controller/IssueController.java
@@ -88,6 +88,16 @@ return this.setSuccessMessage(resJsonData); } // 하위 이슈 추가 @RequestMapping(value = "/issue/modifyParentIssue", produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody Map<String, Object> modifyParentIssue(@RequestBody Map<String, Map<String, Object>> params) { Map<String, Object> resJsonData = new HashMap<>(); this.issueService.modifyParentIssue(IssueForm.make(params.get(Constants.REQ_KEY_CONTENT))); return this.setSuccessMessage(resJsonData); } // 이슈 삭제 @RequestMapping(value = "/issue/remove", produces = MediaType.APPLICATION_JSON_VALUE) public src/main/java/kr/wisestone/owl/web/form/IssueForm.java
@@ -40,6 +40,7 @@ private Long companyId; //업체필드 private Long ispId; //ISP필드 private Long hostingId; //호스팅필드 private Long parentIssueId; // 상위 이슈 public IssueForm() { } @@ -312,4 +313,12 @@ public void setHostingId(Long hostingId) { this.hostingId = hostingId; } public Long getParentIssueId() { return parentIssueId; } public void setParentIssueId(Long parentIssueId) { this.parentIssueId = parentIssueId; } } src/main/resources/migration/V1_11__Alter_Table.sql
@@ -127,3 +127,5 @@ -- 이슈 타입 프로젝트 ALTER TABLE `issue_type` ADD COLUMN `project_id` BIGINT(11) NULL; -- 상위 이슈 ALTER TABLE `issue` ADD COLUMN `parent_issue_id` BIGINT(11) NULL; src/main/webapp/custom_components/js-table/tableColumnGenerator.directive.js
@@ -199,9 +199,14 @@ break; // 연관일감 이동 // 연관 일감 이동 case "ISSUE_RELATION_MOVE" : makeTag += "<span class=\"titlename cursor\" ng-click=\"event.changeDetailView(data.issueRelation)\">" + scope.data.title + "</span></a>"; break; // 연관 일감 이동 case "ISSUE_DOWN_MOVE" : makeTag += "<span class=\"titlename cursor\" ng-click=\"event.changeDetailView(data.id)\">" + scope.data.title + "</span></a>"; break; // 연관일감 구분 @@ -214,6 +219,11 @@ makeTag += '<img class="cursor" src="/assets/images/delete-icon.png" ng-click="event.removeRelationIssue(data.id)">'; break; // 하위 일감 삭제 case "ISSUE_DOWN_DELETE": makeTag += '<img class="cursor" src="/assets/images/delete-icon.png" ng-click="event.removeDownIssue(data.id)">'; break; // 이름을 클릭하면 수정 팝업 표시 case "COMMON_MODIFY" : if (scope.data.modifyPermissionCheck) { src/main/webapp/i18n/ko/global.json
@@ -293,7 +293,15 @@ "normalList" : "이슈 목록", "timeLine" : "타임 라인", "useProjects" : "사용 프로젝트", "companyInfo" : "업체/ISP/호스팅" "companyInfo" : "업체/ISP/호스팅", "downIssue": "하위 이슈", "downIssueTitle": "하위 이슈 제목", "downIssueType": "하위 이슈 구분", "addDownIssue": "하위 이슈 추가", "downIssueRemove" : "하위 이슈 삭제", "failedToIssueAddIssueDown": "하위 이슈 추가 실패", "failedToIssueDeleteIssueDown": "하위 이슈 삭제 실패", "errorSelectDownIssue" : "하위 이슈가 선택되지 않았습니다." }, "project": { "createProject": "프로젝트 만들기", src/main/webapp/scripts/app/issue/issueDetail.controller.js
@@ -32,6 +32,7 @@ $scope.fn.reservation = reservation; // 예약 정보를 확인 및 변경 한다. $scope.fn.getIssueListCallBack = getIssueListCallBack; $scope.fn.addRelationIssue = addRelationIssue; // 연관 이슈 추가 $scope.fn.addDownIssue = addDownIssue; // 하위 이슈 추가 // 이슈 목록 컨트롤러 vm, fn 상속 중 @@ -61,10 +62,20 @@ $scope.vm.relationIssueType = $scope.vm.relationIssueTypes[0]; $scope.vm.form = { issues : [] issues : [], //연관 일감 issuesDown : [] // 하위 일감 }; $scope.vm.issueName = ""; $scope.vm.issueNameDown = ""; // 선택된 하위 일감 이름 $scope.vm.autoCompletePageDown = { issue : { page : 0, totalPage : 0 }, }; $scope.vm.issueName = ""; // 선택된 연관 일감 이름 $scope.vm.autoCompletePage = { issue : { page : 0, @@ -80,10 +91,60 @@ changeDetailView : changeDetailView }; $scope.vm.downResponseData = []; $scope.vm.downTableConfigs = []; // 테이블 이벤트 $scope.downTableEvent = { removeDownIssue : removeDownIssue, // 연관 일감 삭제 changeDetailView : changeDetailView }; function changeDetailView(issue) { // 이슈 번호를 저장한 후 이슈 목록으로 이동한다. // $rootScope.$broadcast("makeIssueSearch", issue); $scope.$parent.tableEvent.changeDetailView(issue.id); } // 하위 일감 삭제 function removeDownIssue(id) { // 삭제 알림 SweetAlert.swal({ title : $filter("translate")("issue.downIssueRemove"), // 연관 일감 삭제 text : $filter("translate")("issue.wantToDeleteSelectIssue"), type : "warning", showCancelButton : true, confirmButtonColor : "#DD6B55", confirmButtonText : $filter("translate")("common.delete"), // 삭제 cancelButtonText : $filter("translate")("common.cancel"), // 취소 closeOnConfirm : false, closeOnCancel : true }, function (isConfirm) { SweetAlert.close(); if (isConfirm) { $rootScope.spinner = true; var contents = { id : id }; Issue.modifyParentIssue($resourceProvider.getContent( contents, $resourceProvider.getPageContent(0, 10))).then(function (result) { if (result.data.message.status === "success") { $scope.fn.getIssueDetail(); } else { SweetAlert.error($filter("translate")("issue.failedToIssueDeleteIssueDown"), result.data.message.message); // "연관일감 삭제 실패" } $rootScope.spinner = false; }); } }); } // 연관 일감 삭제 @@ -133,7 +194,25 @@ $scope.vm.autoCompletePage.issue.totalPage = result.data.page.totalPage; } // 이슈 테이블 설정 // 하위 이슈 테이블 설정 function makeTableConfigsDown() { $scope.vm.downTableConfigs = []; $scope.vm.downTableConfigs.push($tableProvider.config() .setHName("issue.downIssueTitle") .setDType("renderer") .setHWidth("width-60 bold") .setHSort(false) .setDRenderer("ISSUE_DOWN_MOVE")) $scope.vm.downTableConfigs.push($tableProvider.config() .setHName("issue.relationIssueDelete") .setDType("renderer") .setHWidth("width-10 bold") .setDRenderer("ISSUE_DOWN_DELETE") .setHSort(false) .setDAlign("text-center")) } // 연관 이슈 테이블 설정 function makeTableConfigs() { $scope.vm.relTableConfigs = []; $scope.vm.relTableConfigs.push($tableProvider.config() @@ -175,6 +254,32 @@ // } // }); // } // 하위 이슈 추가 function addDownIssue() { if ($scope.vm.issueNameDown.length == 0 || $scope.vm.form.issuesDown.length == 0 || $scope.vm.issueNameDown != $scope.vm.form.issuesDown[0].title) { SweetAlert.error($filter("translate")("issue.errorSelectDownIssue"), ""); return; } var contents = { id : $scope.vm.form.issuesDown[0].id, parentIssueId : $rootScope.currentDetailIssueId }; Issue.modifyParentIssue($resourceProvider.getContent( contents, $resourceProvider.getPageContent(0, 10))).then(function (result) { if (result.data.message.status === "success") { $scope.fn.getIssueDetail(); } else { SweetAlert.error($filter("translate")("issue.failedToIssueAddIssueDown"), result.data.message.message); // "연관일감 생성 실패" } }); } // 연관 이슈 추가 @@ -316,6 +421,8 @@ $scope.vm.issueName = ""; $scope.vm.form.issues = []; $scope.vm.form.issues.push(result.data.data); $scope.vm.form.issuesDown = []; $scope.vm.form.issuesDown.push(result.data.data); makeTableConfigs(); angular.forEach(result.data.data.issueRelationVos, function (issueRelationVo){ @@ -323,7 +430,13 @@ $scope.vm.form.issues.push(issueRelationVo.issueRelation); }); makeTableConfigsDown(); angular.forEach(result.data.data.issueDownVos, function (issueDownVo){ $scope.vm.form.issuesDown.push(issueDownVo.issue); }); $scope.vm.viewer.issueRelationVos = result.data.data.issueRelationVos; $scope.vm.viewer.issueDownVos = result.data.data.issueDownVos; } } else { src/main/webapp/scripts/components/issue/issue.service.js
@@ -44,6 +44,12 @@ return response; }); }, modifyParentIssue : function (conditions) { return $http.post("issue/modifyParentIssue", conditions).then(function (response) { $log.debug("상위 일감 수정 결과 : ", response); return response; }); }, modify : function (conditions) { conditions.url = "issue/modify"; return $upload.upload(conditions).progress(function (evt) { src/main/webapp/views/issue/issueDetail.html
@@ -1,10 +1,12 @@ <!-- 이슈 목록 --> <!-- <div class="support-tickets"> <div class="support-tickets-header"> <!-- <div class="tickets-control">--> <!-- <h5 translate="issue.issueList">--> <!-- 이슈 목록--> <!-- </h5>--> <!-- </div>--> <div class="tickets-control"> <h5 translate="issue.issueList"> 이슈 목록 </h5> </div> <div class="tickets-filter"> <div class="dataTables_length"> @@ -50,8 +52,9 @@ </div> </div> <!-- 테이블 --> --> <!-- 테이블 --> <!-- <div class="support-ticket "> <div class="st-body"> <div class="table-responsive"> @@ -60,8 +63,9 @@ </div> </div> </div> --> <!-- 페이징 --> <!-- <div class="controls-below-table text-center"> <ul uib-pagination boundary-links-numbes="true" @@ -78,7 +82,7 @@ </ul> </div> </div> --> <!-- 상세 화면 --> <div class="support-ticket-content-w" ng-controller="issueDetailController"> <div class="support-ticket-content"> @@ -255,7 +259,6 @@ </div> <h6 class="todo-content-subheader mt-20" translate="issue.relationIssue">연관 일감</h6> <!-- 테이블 --> <div class="mt-10 issue-detail-word-break width-100"> <js-table data="vm.viewer.issueRelationVos" table-configs="vm.relTableConfigs" @@ -294,6 +297,33 @@ </div> </div> <h6 class="todo-content-subheader mt-20" translate="issue.downIssue">하위 일감</h6> <!-- 테이블 --> <div class="mt-10 issue-detail-word-break width-100"> <js-table data="vm.viewer.issueDownVos" table-configs="vm.downTableConfigs" event="downTableEvent" detail-view="true" hide-header="false" use-sort="false"></js-table> <div class="row"> <div class="col-sm-6"> <js-autocomplete-single data-input-name="issue" selected-model="vm.form.issuesDown" search="vm.issueNameDown" source="fn.getIssueList(vm.issueNameDown, vm.form.issuesDown, vm.autoCompletePageDown.issue.page, fn.getIssueListCallBack)" page="vm.autoCompletePageDown.issue.page" total-page="vm.autoCompletePageDown.issue.totalPage" input-disabled="false" translation-texts="{ empty : 'common.emptyIssue' }" extra-settings="{ displayProp : 'title' , idProp : 'id', imageable : false, imagePathProp : '', type : '', maxlength : 200, autoResize : true, stopRemoveBodyEvent : true }"></js-autocomplete-single> </div> <div class="col-auto vertical-middle"> <button type="button" class="btn btn-primary form-control input-sm" ng-click="fn.addDownIssue()" translate="issue.addDownIssue">추가</button> </div> </div> </div> <h6 class="todo-content-subheader mt-20" translate="common.content">내용</h6> <div class="box mt-10 issue-detail-word-break width-100" >