Merge branch '117-filter-users' into 'dev'

Resolve "Фильтрация участников гранта по двум новым параметрам"

Closes #117

See merge request romanov73/ng-tracker!77
This commit is contained in:
Anton Romanov 2019-05-03 05:58:19 +00:00
commit 68737b976f
10 changed files with 251 additions and 59 deletions

View File

@ -1,5 +1,7 @@
package ru.ulstu.grant.model;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.UserContainer;
@ -66,10 +68,11 @@ public class Grant extends BaseEntity implements UserContainer {
private String comment;
//Заявка на грант
@ManyToOne
@JoinColumn(name = "file_id")
private FileData application;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "grant_id", unique = true)
@Fetch(FetchMode.SUBSELECT)
private List<FileData> files = new ArrayList<>();
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "project_id")
@ -113,12 +116,12 @@ public class Grant extends BaseEntity implements UserContainer {
this.comment = comment;
}
public FileData getApplication() {
return application;
public List<FileData> getFiles() {
return files;
}
public void setApplication(FileData application) {
this.application = application;
public void setFiles(List<FileData> files) {
this.files = files;
}
public String getTitle() {

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.NotEmpty;
import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.file.model.FileDataDto;
import ru.ulstu.paper.model.Paper;
import ru.ulstu.project.model.ProjectDto;
import ru.ulstu.user.model.UserDto;
@ -25,7 +26,7 @@ public class GrantDto {
private Grant.GrantStatus status;
private List<Deadline> deadlines = new ArrayList<>();
private String comment;
private String applicationFileName;
private List<FileDataDto> files = new ArrayList<>();
private ProjectDto project;
private Set<Integer> authorIds;
private Set<UserDto> authors;
@ -33,6 +34,8 @@ public class GrantDto {
private boolean wasLeader;
private boolean hasAge;
private boolean hasDegree;
private boolean hasBAKPapers;
private boolean hasScopusPapers;
private List<Integer> paperIds = new ArrayList<>();
private List<Paper> papers = new ArrayList<>();
private List<Integer> removedDeadlineIds = new ArrayList<>();
@ -47,6 +50,7 @@ public class GrantDto {
@JsonProperty("status") Grant.GrantStatus status,
@JsonProperty("deadlines") List<Deadline> deadlines,
@JsonProperty("comment") String comment,
@JsonProperty("files") List<FileDataDto> files,
@JsonProperty("project") ProjectDto project,
@JsonProperty("authorIds") Set<Integer> authorIds,
@JsonProperty("authors") Set<UserDto> authors,
@ -61,8 +65,9 @@ public class GrantDto {
this.status = status;
this.deadlines = deadlines;
this.comment = comment;
this.applicationFileName = null;
this.files = files;
this.project = project;
this.authorIds = authorIds;
this.authors = authors;
this.leaderId = leaderId;
this.wasLeader = wasLeader;
@ -78,8 +83,8 @@ public class GrantDto {
this.status = grant.getStatus();
this.deadlines = grant.getDeadlines();
this.comment = grant.getComment();
this.files = convert(grant.getFiles(), FileDataDto::new);
this.project = grant.getProject() == null ? null : new ProjectDto(grant.getProject());
this.applicationFileName = grant.getApplication() == null ? null : grant.getApplication().getName();
this.authorIds = convert(grant.getAuthors(), user -> user.getId());
this.authors = convert(grant.getAuthors(), UserDto::new);
this.leaderId = grant.getLeader().getId();
@ -130,6 +135,14 @@ public class GrantDto {
this.comment = comment;
}
public List<FileDataDto> getFiles() {
return files;
}
public void setFiles(List<FileDataDto> files) {
this.files = files;
}
public ProjectDto getProject() {
return project;
}
@ -138,14 +151,6 @@ public class GrantDto {
this.project = project;
}
public String getApplicationFileName() {
return applicationFileName;
}
public void setApplicationFileName(String applicationFileName) {
this.applicationFileName = applicationFileName;
}
public Set<Integer> getAuthorIds() {
return authorIds;
}
@ -224,4 +229,20 @@ public class GrantDto {
public void setRemovedDeadlineIds(List<Integer> removedDeadlineIds) {
this.removedDeadlineIds = removedDeadlineIds;
}
public boolean isHasBAKPapers() {
return hasBAKPapers;
}
public void setHasBAKPapers(boolean hasBAKPapers) {
this.hasBAKPapers = hasBAKPapers;
}
public boolean isHasScopusPapers() {
return hasScopusPapers;
}
public void setHasScopusPapers(boolean hasScopusPapers) {
this.hasScopusPapers = hasScopusPapers;
}
}

View File

@ -5,6 +5,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.deadline.service.DeadlineService;
import ru.ulstu.file.model.FileDataDto;
import ru.ulstu.file.service.FileService;
import ru.ulstu.grant.model.Grant;
import ru.ulstu.grant.model.GrantDto;
@ -19,10 +20,11 @@ import ru.ulstu.user.service.UserService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
import static org.springframework.util.ObjectUtils.isEmpty;
import static ru.ulstu.core.util.StreamApiUtils.convert;
import static ru.ulstu.grant.model.Grant.GrantStatus.APPLICATION;
@ -81,9 +83,9 @@ public class GrantService {
grant.setProject(projectService.findById(grantDto.getProject().getId()));
}
grant.setDeadlines(deadlineService.saveOrCreate(grantDto.getDeadlines()));
if (grantDto.getApplicationFileName() != null) {
grant.setApplication(fileService.createFileFromTmp(grantDto.getApplicationFileName()));
}
grant.setFiles(fileService.saveOrCreate(grantDto.getFiles().stream()
.filter(f -> !f.isDeleted())
.collect(toList())));
grant.getAuthors().clear();
if (grantDto.getAuthorIds() != null && !grantDto.getAuthorIds().isEmpty()) {
grantDto.getAuthorIds().forEach(authorIds -> grant.getAuthors().add(userService.findById(authorIds)));
@ -106,8 +108,10 @@ public class GrantService {
@Transactional
public Integer update(GrantDto grantDto) throws IOException {
Grant grant = grantRepository.findOne(grantDto.getId());
if (grantDto.getApplicationFileName() != null && grant.getApplication() != null) {
fileService.deleteFile(grant.getApplication());
for (FileDataDto file : grantDto.getFiles().stream()
.filter(f -> f.isDeleted() && f.getId() != null)
.collect(toList())) {
fileService.delete(file.getId());
}
grantDto.getRemovedDeadlineIds().forEach(deadlineService::remove);
grantRepository.save(copyFromDto(grant, grantDto));
@ -117,9 +121,6 @@ public class GrantService {
@Transactional
public void delete(Integer grantId) throws IOException {
Grant grant = grantRepository.findOne(grantId);
if (grant.getApplication() != null) {
fileService.deleteFile(grant.getApplication());
}
grantRepository.delete(grant);
}
@ -153,19 +154,28 @@ public class GrantService {
public List<User> getGrantAuthors(GrantDto grantDto) {
List<User> filteredUsers = userService.filterByAgeAndDegree(grantDto.isHasAge(), grantDto.isHasDegree());
if (grantDto.isWasLeader()) {
filteredUsers = filteredUsers
.stream()
.filter(getCompletedGrantLeaders()::contains)
.collect(Collectors.toList());
filteredUsers = checkContains(filteredUsers, getCompletedGrantLeaders());
}
if (grantDto.isHasBAKPapers()) {
filteredUsers = checkContains(filteredUsers, getBAKAuthors());
}
if (grantDto.isHasScopusPapers()) {
filteredUsers = checkContains(filteredUsers, getScopusAuthors());
}
return filteredUsers;
}
private List<User> checkContains(List<User> filteredUsers, List<User> checkUsers) {
return filteredUsers.stream()
.filter(checkUsers::contains)
.collect(toList());
}
private List<User> getCompletedGrantLeaders() {
return grantRepository.findByStatus(Grant.GrantStatus.COMPLETED)
.stream()
.map(Grant::getLeader)
.collect(Collectors.toList());
.collect(toList());
}
public List<Paper> getGrantPapers(List<Integer> paperIds) {
@ -193,4 +203,27 @@ public class GrantService {
grantDto.getDeadlines().remove((int) deadlineId);
}
private List<User> getCompletedPapersAuthors(Paper.PaperType type) {
List<Paper> papers = paperService.findAllCompletedByType(type);
return papers.stream()
.filter(paper -> paper.getAuthors() != null)
.flatMap(paper -> paper.getAuthors().stream())
.collect(toList());
}
private List<User> getBAKAuthors() {
return getCompletedPapersAuthors(Paper.PaperType.VAK)
.stream()
.distinct()
.collect(toList());
}
private List<User> getScopusAuthors() {
List<User> authors = getCompletedPapersAuthors(Paper.PaperType.SCOPUS);
return authors
.stream()
.filter(author -> Collections.frequency(authors, author) > 3)
.collect(toList());
}
}

View File

@ -16,4 +16,6 @@ public interface PaperRepository extends JpaRepository<Paper, Integer> {
List<Paper> findByIdNotIn(List<Integer> paperIds);
List<Paper> findAllByIdIn(List<Integer> paperIds);
List<Paper> findByTypeAndStatus(Paper.PaperType type, Paper.PaperStatus status);
}

View File

@ -303,4 +303,8 @@ public class PaperService {
StringUtils.isEmpty(referenceDto.getPublisher()) ? "" : referenceDto.getPublisher() + ", ",
referenceDto.getPages());
}
public List<Paper> findAllCompletedByType(Paper.PaperType type) {
return paperRepository.findByTypeAndStatus(type, Paper.PaperStatus.COMPLETED);
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="tanya" id="20190430_000000-1">
<dropColumn columnName="file_id" tableName="grants"/>
<addColumn tableName="file">
<column name="grant_id" type="integer"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@ -36,4 +36,5 @@
<include file="db/changelog-20190424_000000-schema.xml"/>
<include file="db/changelog-20190426_000000-schema.xml"/>
<include file="db/changelog-20190428_000000-schema.xml"/>
<include file="db/changelog-20190430_000000-schema.xml"/>
</databaseChangeLog>

View File

@ -28,4 +28,16 @@
.btn-delete-deadline {
color: black;
}
.div-file-name{
padding-top: 6px;
}
.div-loader {
margin-bottom: 5px;
}
.div-row-file {
margin-bottom: 2px;
}

View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
</head>
<body>
<div th:fragment="filesList (isLatexAttach)" th:remove="tag">
<th:block th:each="file, rowStat : *{files}">
<span th:if="${(!isLatexAttach and file.isLatexAttach == null) or file.isLatexAttach == isLatexAttach}"
th:remove="tag">
<div class="row div-row-file" th:id="|files${rowStat.index}|"
th:style="${file.deleted} ? 'display: none;' :''">
<input type="hidden" th:field="*{files[__${rowStat.index}__].id}"/>
<input type="hidden" th:field="*{files[__${rowStat.index}__].deleted}"/>
<input type="hidden" th:field="*{files[__${rowStat.index}__].name}"/>
<input type="hidden" th:field="*{files[__${rowStat.index}__].tmpFileName}"/>
<div class="col-10 div-file-name">
<a th:onclick="${file.id==null} ?
'downloadFile('+${file.tmpFileName}+',null,\''+${file.name}+'\')':
'downloadFile(null,'+${file.id}+',\''+${file.name}+'\')' "
href="javascript:void(0)"
th:text="*{files[__${rowStat.index}__].name}">
</a>
</div>
<div class="col-2">
<a class="btn btn-danger float-right"
th:onclick="|$('#files${rowStat.index}\\.deleted').val('true');
$('#files${rowStat.index}').hide(); |">
<span aria-hidden="true"><i class="fa fa-times"/></span>
</a>
</div>
</div>
</span>
</th:block>
</div>
</body>
</html>

View File

@ -61,7 +61,7 @@
</div>
<div class="col-2">
<button type="submit"
class="btn btn-danger float-right btn-delete-deadline "
class="btn btn-danger float-right btn-delete-deadline"
id="removeDeadline" name="removeDeadline"
th:value="${rowStat.index}">
<span aria-hidden="true">
@ -77,12 +77,14 @@
<input type="submit" id="addDeadline" name="addDeadline" class="btn btn-primary"
value="Добавить дедлайн"/>
</div>
<div class="form-group">
<label for="loader">Загрузить заявку:</label>
<div class="form-group div-loader">
<label for="loader">Загрузить файлы:</label>
<div id="loader">
</div>
</div>
<div class="form-group" id="files-list">
<div th:replace="grants/fragments/grantFilesListFragment :: filesList(isLatexAttach = ${false})"/>
</div>
</div>
<div class="col-md-6 col-sm-12">
<label data-toggle="collapse"
@ -90,7 +92,8 @@
aria-expanded="false"
aria-controls="collapse-filter">Фильтр рабочей группы
</label>
<div th:class="${grantDto.wasLeader || grantDto.hasAge || grantDto.hasDegree} ?
<div th:class="${grantDto.wasLeader || grantDto.hasAge || grantDto.hasDegree
|| grantDto.hasBAKPapers || grantDto.hasScopusPapers} ?
'form-check' : 'form-check collapse'" id="collapse-filter">
<div class="row">
<div class="col">
@ -111,14 +114,16 @@
<label class="form-check-label" for="f3">епень к.т.н.</label>
</div>
<div class="col">
<input class="form-check-input" type="checkbox" id="f4"/>
<input class="form-check-input" type="checkbox" id="f4"
th:field="*{hasScopusPapers}" th:onclick="|$('#filter').click();|"/>
<label class="form-check-label" for="f4">Более 3-х публикаций в
scopus</label>
</div>
</div>
<div class="row">
<div class="col">
<input class="form-check-input" type="checkbox" id="f5"/>
<input class="form-check-input" type="checkbox" id="f5"
th:field="*{hasBAKPapers}" th:onclick="|$('#filter').click();|"/>
<label class="form-check-label" for="f5">Наличие ВАК статей</label> <br/>
</div>
<div class="col">
@ -233,20 +238,100 @@
new FileLoader({
div: "loader",
url: urlFileUpload,
maxSize: 1.5,
extensions: ["doc", "docx", "xls", "jpg", "pdf", "txt", "png"],
maxSize: -1,
extensions: [],
callback: function (response) {
showFeedbackMessage("Файл успешно загружен");
console.debug(response);
addNewFile(response);
}
});
$('.selectpicker').selectpicker();
});
/*]]>*/
function addNewFile(fileDto) {
var fileNumber = $("#files-list div.row").length;
var newFileRow = $("<div/>")
.attr("id", 'files' + fileNumber)
.addClass("row div-row-file");
var idInput = $("<input/>")
.attr("type", "hidden")
.attr("id", "files" + fileNumber + ".id")
.attr("value", '')
.attr("name", "files[" + fileNumber + "].id");
newFileRow.append(idInput);
var flagInput = $("<input/>")
.attr("type", "hidden")
.attr("id", "files" + fileNumber + ".deleted")
.attr("value", "false")
.attr("name", "files[" + fileNumber + "].deleted");
newFileRow.append(flagInput);
var nameInput = $("<input/>")
.attr("type", "hidden")
.attr("id", "files" + fileNumber + ".name")
.attr("value", fileDto.fileName)
.attr("name", "files[" + fileNumber + "].name");
newFileRow.append(nameInput);
var tmpFileNameInput = $("<input/>")
.attr("type", "hidden")
.attr("id", "files" + fileNumber + ".tmpFileName")
.attr("value", fileDto.tmpFileName)
.attr("name", "files[" + fileNumber + "].tmpFileName");
newFileRow.append(tmpFileNameInput);
var nameDiv = $("<div/>")
.addClass("col-10 div-file-name")
.append($("<a/>").text(fileDto.fileName)
.attr("href", 'javascript:void(0)')
.attr("onclick", "downloadFile('" + fileDto.tmpFileName + "',null,'" + fileDto.fileName + "')"));
newFileRow.append(nameDiv);
var nextDiv = $("<div/>")
.addClass("col-2");
var nextA = $("<a/>")
.addClass("btn btn-danger float-right")
.attr("onclick", "$('#files" + fileNumber + "\\\\.deleted').val('true'); $('#files" + fileNumber + "').hide();")
.append(($("<span/>").attr("aria-hidden", "true")).append($("<i/>").addClass("fa fa-times")))
;
nextDiv.append(nextA)
newFileRow.append(nextDiv);
$("#files-list").append(newFileRow);
}
function downloadFile(tmpName, fileId, downloadName) {
let xhr = new XMLHttpRequest();
if (fileId != null) xhr.open('GET', urlFileDownload + fileId);
if (tmpName != null) xhr.open('GET', urlFileDownloadTmp + tmpName);
xhr.responseType = 'blob';
var formData = new FormData();
if (fileId != null) formData.append("file-id", fileId);
if (tmpName != null) formData.append("tmp-file-name", tmpName);
xhr.send(formData);
xhr.onload = function () {
if (this.status == 200) {
console.debug(this.response);
var blob = new Blob([this.response], {type: '*'});
let a = document.createElement("a");
a.style = "display: none";
document.body.appendChild(a);
let url = window.URL.createObjectURL(blob);
a.href = url;
a.download = downloadName;
a.click();
window.URL.revokeObjectURL(url);
} else {
}
}
}
</script>
<script type="text/javascript">
function updateAuthors() {
@ -256,23 +341,6 @@
var lid = $("#leaderId option:selected").val();
$("#authors [value='" + lid + "']").attr("disabled", "disabled");
}
</script>
<script>
function viewdiv(id) {
var el = document.getElementById(id);
var link = document.getElementById('toggleLink');
if (el.style.display == "block") {
el.style.display = "none";
link.innerText = link.getAttribute('data-text-hide');
} else {
el.style.display = "block";
link.innerText = link.getAttribute('data-text-show');
}
}
</script>
</div>
</body>