Merge branch '70-notification-conf' into 'dev'

Resolve "Оповещения о дедлайнах конференции"

Closes #70

See merge request romanov73/ng-tracker!76
This commit is contained in:
Anton Romanov 2019-05-03 05:56:33 +00:00
commit 6eed8591b4
22 changed files with 496 additions and 27 deletions

View File

@ -13,7 +13,6 @@ import ru.ulstu.conference.model.ConferenceDto;
import ru.ulstu.conference.model.ConferenceFilterDto; import ru.ulstu.conference.model.ConferenceFilterDto;
import ru.ulstu.conference.model.ConferenceUser; import ru.ulstu.conference.model.ConferenceUser;
import ru.ulstu.conference.service.ConferenceService; import ru.ulstu.conference.service.ConferenceService;
import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import springfox.documentation.annotations.ApiIgnore; import springfox.documentation.annotations.ApiIgnore;
@ -22,9 +21,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.util.StringUtils.isEmpty;
import static ru.ulstu.core.controller.Navigation.CONFERENCES_PAGE; import static ru.ulstu.core.controller.Navigation.CONFERENCES_PAGE;
import static ru.ulstu.core.controller.Navigation.CONFERENCE_PAGE; import static ru.ulstu.core.controller.Navigation.CONFERENCE_PAGE;
import static ru.ulstu.core.controller.Navigation.REDIRECT_TO; import static ru.ulstu.core.controller.Navigation.REDIRECT_TO;
@ -77,7 +74,8 @@ public class ConferenceController {
@PostMapping(value = "/conference", params = "save") @PostMapping(value = "/conference", params = "save")
public String save(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException { public String save(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException {
filterEmptyDeadlines(conferenceDto); conferenceService.filterEmptyDeadlines(conferenceDto);
conferenceService.checkEmptyFieldsOfDeadline(conferenceDto, errors);
if (errors.hasErrors()) { if (errors.hasErrors()) {
return CONFERENCE_PAGE; return CONFERENCE_PAGE;
} }
@ -87,11 +85,11 @@ public class ConferenceController {
@PostMapping(value = "/conference", params = "addDeadline") @PostMapping(value = "/conference", params = "addDeadline")
public String addDeadline(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException { public String addDeadline(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException {
filterEmptyDeadlines(conferenceDto); conferenceService.filterEmptyDeadlines(conferenceDto);
if (errors.hasErrors()) { if (errors.hasErrors()) {
return CONFERENCE_PAGE; return CONFERENCE_PAGE;
} }
conferenceDto.getDeadlines().add(new Deadline()); conferenceService.addDeadline(conferenceDto);
return CONFERENCE_PAGE; return CONFERENCE_PAGE;
} }
@ -167,9 +165,4 @@ public class ConferenceController {
return years; return years;
} }
private void filterEmptyDeadlines(ConferenceDto conferenceDto) {
conferenceDto.setDeadlines(conferenceDto.getDeadlines().stream()
.filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription()))
.collect(Collectors.toList()));
}
} }

View File

@ -21,8 +21,10 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional;
@Entity @Entity
@Table(name = "conference") @Table(name = "conference")
@ -57,6 +59,7 @@ public class Conference extends BaseEntity {
@JoinTable(name = "paper_conference", @JoinTable(name = "paper_conference",
joinColumns = {@JoinColumn(name = "conference_id")}, joinColumns = {@JoinColumn(name = "conference_id")},
inverseJoinColumns = {@JoinColumn(name = "paper_id")}) inverseJoinColumns = {@JoinColumn(name = "paper_id")})
@Fetch(FetchMode.SUBSELECT)
private List<Paper> papers = new ArrayList<>(); private List<Paper> papers = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@ -135,4 +138,13 @@ public class Conference extends BaseEntity {
public void setUsers(List<ConferenceUser> users) { public void setUsers(List<ConferenceUser> users) {
this.users = users; this.users = users;
} }
public Optional<Deadline> getNextDeadline() {
return deadlines
.stream()
.filter(deadline -> deadline.getDate() != null)
.sorted(Comparator.comparing(Deadline::getDate))
.filter(d -> d.getDate().after(new Date()))
.findFirst();
}
} }

View File

@ -1,7 +1,113 @@
package ru.ulstu.conference.service; package ru.ulstu.conference.service;
import com.google.common.collect.ImmutableMap;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.ulstu.conference.model.Conference;
import ru.ulstu.core.util.DateUtils;
import ru.ulstu.ping.service.PingService;
import ru.ulstu.user.service.MailService;
import ru.ulstu.user.service.UserService;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Service @Service
public class ConferenceNotificationService { public class ConferenceNotificationService {
private final static int YESTERDAY = -1;
private final static int DAYS_TO_DEADLINE_NOTIFICATION = 7;
private final static String TEMPLATE_PING = "conferencePingNotification";
private final static String TEMPLATE_DEADLINE = "conferenceDeadlineNotification";
private final static String TEMPLATE_CREATE = "conferenceCreateNotification";
private final static String TEMPLATE_UPDATE_DEADLINES = "conferenceUpdateDeadlinesNotification";
private final static String TEMPLATE_UPDATE_DATES = "conferenceUpdateDatesNotification";
private final static String TITLE_PING = "Обратите внимание на конференцию: %s";
private final static String TITLE_DEADLINE = "Приближается дедлайн конференции: %s";
private final static String TITLE_CREATE = "Создана новая конференция: %s";
private final static String TITLE_UPDATE_DEADLINES = "Изменения дедлайнов в конференции: %s";
private final static String TITLE_UPDATE_DATES = "Изменение дат проведения конференции: %s";
private final MailService mailService;
private final UserService userService;
private final PingService pingService;
public ConferenceNotificationService(MailService mailService,
UserService userService,
PingService pingService) {
this.mailService = mailService;
this.userService = userService;
this.pingService = pingService;
}
public void sendDeadlineNotifications(List<Conference> conferences) {
Date now = DateUtils.addDays(new Date(), DAYS_TO_DEADLINE_NOTIFICATION);
conferences
.stream()
.filter(conference -> needToSendDeadlineNotification(conference, now))
.forEach(this::sendMessageDeadline);
}
private boolean needToSendDeadlineNotification(Conference conference, Date compareDate) {
return (conference.getNextDeadline().isPresent())
&& conference.getNextDeadline().get().getDate().after(new Date())
&& conference.getNextDeadline().get().getDate().before(compareDate);
}
private void sendMessageDeadline(Conference conference) {
Map<String, Object> variables = ImmutableMap.of("conference", conference);
sendForAllParticipants(variables, conference, TEMPLATE_DEADLINE, String.format(TITLE_DEADLINE, conference.getTitle()));
}
public void sendCreateNotification(Conference conference) {
Map<String, Object> variables = ImmutableMap.of("conference", conference);
sendForAllUsers(variables, TEMPLATE_CREATE, String.format(TITLE_CREATE, conference.getTitle()));
}
public void updateDeadlineNotification(Conference conference) {
Map<String, Object> variables = ImmutableMap.of("conference", conference);
sendForAllParticipants(variables, conference, TEMPLATE_UPDATE_DEADLINES, String.format(TITLE_UPDATE_DEADLINES, conference.getTitle()));
}
public void updateConferencesDatesNotification(Conference conference, Date oldBeginDate, Date oldEndDate) {
Map<String, Object> variables = ImmutableMap.of("conference", conference, "oldBeginDate", oldBeginDate, "oldEndDate", oldEndDate);
sendForAllParticipants(variables, conference, TEMPLATE_UPDATE_DATES, String.format(TITLE_UPDATE_DATES, conference.getTitle()));
}
private void sendForAllUsers(Map<String, Object> variables, String template, String title) {
userService.findAll().forEach(user -> mailService.sendEmailFromTemplate(variables, user, template, title));
}
private void sendForAllParticipants(Map<String, Object> variables, Conference conference, String template, String title) {
conference.getUsers().forEach(conferenceUser -> mailService.sendEmailFromTemplate(variables, conferenceUser.getUser(), template, title));
}
public void sendPingNotifications(List<Conference> conferences) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DAY_OF_MONTH, YESTERDAY);
conferences
.stream()
.filter(conference -> {
Integer pingCount = pingService.countPingYesterday(conference, calendar);
return needToSendPingNotification(conference, pingCount);
})
.forEach(this::sendMessagePing);
}
private boolean needToSendPingNotification(Conference conference, Integer pingCount) {
if (pingCount > 0) {
conference.setPing((Integer) pingCount);
return true;
}
return false;
}
private void sendMessagePing(Conference conference) {
Map<String, Object> variables = ImmutableMap.of("conference", conference);
sendForAllParticipants(variables, conference, TEMPLATE_PING, String.format(TITLE_PING, conference.getTitle()));
}
} }

View File

@ -0,0 +1,37 @@
package ru.ulstu.conference.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ConferenceScheduler {
private final static boolean IS_DEADLINE_NOTIFICATION_BEFORE_WEEK = true;
private final Logger log = LoggerFactory.getLogger(ConferenceScheduler.class);
private final ConferenceNotificationService conferenceNotificationService;
private final ConferenceService conferenceService;
public ConferenceScheduler(ConferenceNotificationService conferenceNotificationService,
ConferenceService conferenceService) {
this.conferenceNotificationService = conferenceNotificationService;
this.conferenceService = conferenceService;
}
@Scheduled(cron = "0 0 8 * * MON", zone = "Europe/Samara")
public void checkDeadlineBeforeWeek() {
log.debug("ConferenceScheduler.checkDeadlineBeforeWeek started");
conferenceNotificationService.sendDeadlineNotifications(conferenceService.findAll());
log.debug("ConferenceScheduler.checkDeadlineBeforeWeek finished");
}
@Scheduled(cron = "0 0 8 * * *", zone = "Europe/Samara")
public void checkNewPing() {
log.debug("ConferenceScheduler.checkPing started");
conferenceNotificationService.sendPingNotifications(conferenceService.findAll());
log.debug("ConferenceScheduler.checkPing finished");
}
}

View File

@ -5,15 +5,18 @@ import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import ru.ulstu.conference.model.Conference; import ru.ulstu.conference.model.Conference;
import ru.ulstu.conference.model.ConferenceDto; import ru.ulstu.conference.model.ConferenceDto;
import ru.ulstu.conference.model.ConferenceFilterDto; import ru.ulstu.conference.model.ConferenceFilterDto;
import ru.ulstu.conference.model.ConferenceUser; import ru.ulstu.conference.model.ConferenceUser;
import ru.ulstu.conference.repository.ConferenceRepository; import ru.ulstu.conference.repository.ConferenceRepository;
import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.deadline.service.DeadlineService; import ru.ulstu.deadline.service.DeadlineService;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.paper.service.PaperService; import ru.ulstu.paper.service.PaperService;
import ru.ulstu.ping.service.PingService; import ru.ulstu.ping.service.PingService;
import ru.ulstu.timeline.service.EventService;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
import ru.ulstu.user.service.UserService; import ru.ulstu.user.service.UserService;
@ -22,6 +25,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.util.ObjectUtils.isEmpty; import static org.springframework.util.ObjectUtils.isEmpty;
import static ru.ulstu.core.util.StreamApiUtils.convert; import static ru.ulstu.core.util.StreamApiUtils.convert;
@ -36,19 +40,25 @@ public class ConferenceService {
private final PaperService paperService; private final PaperService paperService;
private final UserService userService; private final UserService userService;
private final PingService pingService; private final PingService pingService;
private final ConferenceNotificationService conferenceNotificationService;
private final EventService eventService;
public ConferenceService(ConferenceRepository conferenceRepository, public ConferenceService(ConferenceRepository conferenceRepository,
ConferenceUserService conferenceUserService, ConferenceUserService conferenceUserService,
DeadlineService deadlineService, DeadlineService deadlineService,
PaperService paperService, PaperService paperService,
UserService userService, UserService userService,
PingService pingService) { PingService pingService,
ConferenceNotificationService conferenceNotificationService,
EventService eventService) {
this.conferenceRepository = conferenceRepository; this.conferenceRepository = conferenceRepository;
this.conferenceUserService = conferenceUserService; this.conferenceUserService = conferenceUserService;
this.deadlineService = deadlineService; this.deadlineService = deadlineService;
this.paperService = paperService; this.paperService = paperService;
this.userService = userService; this.userService = userService;
this.pingService = pingService; this.pingService = pingService;
this.conferenceNotificationService = conferenceNotificationService;
this.eventService = eventService;
} }
public ConferenceDto getExistConferenceById(Integer id) { public ConferenceDto getExistConferenceById(Integer id) {
@ -91,13 +101,25 @@ public class ConferenceService {
public Integer create(ConferenceDto conferenceDto) throws IOException { public Integer create(ConferenceDto conferenceDto) throws IOException {
Conference newConference = copyFromDto(new Conference(), conferenceDto); Conference newConference = copyFromDto(new Conference(), conferenceDto);
newConference = conferenceRepository.save(newConference); newConference = conferenceRepository.save(newConference);
conferenceNotificationService.sendCreateNotification(newConference);
eventService.createFromConference(newConference);
return newConference.getId(); return newConference.getId();
} }
@Transactional @Transactional
public Integer update(ConferenceDto conferenceDto) throws IOException { public Integer update(ConferenceDto conferenceDto) throws IOException {
Conference conference = conferenceRepository.findOne(conferenceDto.getId()); Conference conference = conferenceRepository.findOne(conferenceDto.getId());
List<Deadline> oldDeadlines = conference.getDeadlines().stream()
.map(this::copyDeadline)
.collect(Collectors.toList());
Date oldBeginDate = conference.getBeginDate();
Date oldEndDate = conference.getEndDate();
conferenceRepository.save(copyFromDto(conference, conferenceDto)); conferenceRepository.save(copyFromDto(conference, conferenceDto));
eventService.updateConferenceDeadlines(conference);
sendNotificationAfterUpdateDeadlines(conference, oldDeadlines);
if (!conference.getBeginDate().equals(oldBeginDate) || !conference.getEndDate().equals(oldEndDate)) {
conferenceNotificationService.updateConferencesDatesNotification(conference, oldBeginDate, oldEndDate);
}
conferenceDto.getRemovedDeadlineIds().forEach(deadlineService::remove); conferenceDto.getRemovedDeadlineIds().forEach(deadlineService::remove);
return conference.getId(); return conference.getId();
} }
@ -109,6 +131,11 @@ public class ConferenceService {
} }
} }
public void addDeadline(ConferenceDto conferenceDto) {
conferenceDto.getDeadlines().add(new Deadline());
}
public void removeDeadline(ConferenceDto conferenceDto, Integer deadlineIndex) throws IOException { public void removeDeadline(ConferenceDto conferenceDto, Integer deadlineIndex) throws IOException {
if (conferenceDto.getDeadlines().get(deadlineIndex).getId() != null) { if (conferenceDto.getDeadlines().get(deadlineIndex).getId() != null) {
conferenceDto.getRemovedDeadlineIds().add(conferenceDto.getDeadlines().get(deadlineIndex).getId()); conferenceDto.getRemovedDeadlineIds().add(conferenceDto.getDeadlines().get(deadlineIndex).getId());
@ -224,4 +251,39 @@ public class ConferenceService {
modelMap.addAttribute("nearshoreSales", nearshoreSales); modelMap.addAttribute("nearshoreSales", nearshoreSales);
modelMap.addAttribute("offshoreSales", offshoreSales); modelMap.addAttribute("offshoreSales", offshoreSales);
} }
public void sendNotificationAfterUpdateDeadlines(Conference conference, List<Deadline> oldDeadlines) {
if (oldDeadlines.size() != conference.getDeadlines().size()) {
conferenceNotificationService.updateDeadlineNotification(conference);
return;
}
if (conference.getDeadlines()
.stream()
.filter(deadline -> !oldDeadlines.contains(deadline))
.count() > 0) {
conferenceNotificationService.updateDeadlineNotification(conference);
}
}
public Deadline copyDeadline(Deadline oldDeadline) {
Deadline newDeadline = new Deadline(oldDeadline.getDate(), oldDeadline.getDescription());
newDeadline.setId(oldDeadline.getId());
return newDeadline;
}
public void checkEmptyFieldsOfDeadline(ConferenceDto conferenceDto, Errors errors) {
for (Deadline deadline : conferenceDto.getDeadlines()) {
if (deadline.getDate() == null || deadline.getDescription().isEmpty()) {
errors.rejectValue("deadlines", "errorCode", "Все поля дедлайна должны быть заполнены");
}
}
}
public void filterEmptyDeadlines(ConferenceDto conferenceDto) {
conferenceDto.setDeadlines(conferenceDto.getDeadlines().stream()
.filter(dto -> dto.getDate() != null || !org.springframework.util.StringUtils.isEmpty(dto.getDescription()))
.collect(Collectors.toList()));
}
} }

View File

@ -9,6 +9,7 @@ import javax.persistence.Entity;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import java.util.Date; import java.util.Date;
import java.util.Objects;
@Entity @Entity
public class Deadline extends BaseEntity { public class Deadline extends BaseEntity {
@ -51,4 +52,26 @@ public class Deadline extends BaseEntity {
public void setDate(Date date) { public void setDate(Date date) {
this.date = date; this.date = date;
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
Deadline deadline = (Deadline) o;
return getId().equals(deadline.getId()) &&
description.equals(deadline.description) &&
date.equals(deadline.date);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), description, date);
}
} }

View File

@ -1,7 +1,13 @@
package ru.ulstu.ping.repository; package ru.ulstu.ping.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.ulstu.conference.model.Conference;
import ru.ulstu.ping.model.Ping; import ru.ulstu.ping.model.Ping;
public interface PingRepository extends JpaRepository<Ping, Integer> { public interface PingRepository extends JpaRepository<Ping, Integer> {
@Query("SELECT count(*) FROM Ping p WHERE (DAY(p.date) = :day) AND (MONTH(p.date) = :month) AND (YEAR(p.date) = :year) AND (p.conference = :conference)")
long countByConferenceAndDate(@Param("conference") Conference conference, @Param("day") Integer day, @Param("month") Integer month, @Param("year") Integer year);
} }

View File

@ -8,6 +8,7 @@ import ru.ulstu.ping.repository.PingRepository;
import ru.ulstu.user.service.UserService; import ru.ulstu.user.service.UserService;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
@Service @Service
@ -27,4 +28,9 @@ public class PingService {
newPing.setConference(conference); newPing.setConference(conference);
pingRepository.save(newPing); pingRepository.save(newPing);
} }
public Integer countPingYesterday(Conference conference, Calendar calendar) {
return Math.toIntExact(pingRepository.countByConferenceAndDate(conference, calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR)));
}
} }

View File

@ -1,6 +1,7 @@
package ru.ulstu.timeline.model; package ru.ulstu.timeline.model;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.conference.model.Conference;
import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.user.model.User; import ru.ulstu.user.model.User;
@ -18,6 +19,7 @@ import javax.persistence.OneToMany;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -62,7 +64,7 @@ public class Event extends BaseEntity {
private String description; private String description;
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(fetch = FetchType.EAGER)
private List<User> recipients; private List<User> recipients = new ArrayList<User>();
@ManyToOne @ManyToOne
@JoinColumn(name = "child_id") @JoinColumn(name = "child_id")
@ -76,6 +78,10 @@ public class Event extends BaseEntity {
@JoinColumn(name = "paper_id") @JoinColumn(name = "paper_id")
private Paper paper; private Paper paper;
@ManyToOne
@JoinColumn(name = "conference_id")
private Conference conference;
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -163,4 +169,12 @@ public class Event extends BaseEntity {
public void setPaper(Paper paper) { public void setPaper(Paper paper) {
this.paper = paper; this.paper = paper;
} }
public Conference getConference() {
return conference;
}
public void setConference(Conference conference) {
this.conference = conference;
}
} }

View File

@ -3,6 +3,7 @@ package ru.ulstu.timeline.model;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
import ru.ulstu.conference.model.ConferenceDto;
import ru.ulstu.paper.model.PaperDto; import ru.ulstu.paper.model.PaperDto;
import ru.ulstu.user.model.UserDto; import ru.ulstu.user.model.UserDto;
@ -25,6 +26,7 @@ public class EventDto {
private final String description; private final String description;
private final List<UserDto> recipients; private final List<UserDto> recipients;
private PaperDto paperDto; private PaperDto paperDto;
private ConferenceDto conferenceDto;
@JsonCreator @JsonCreator
public EventDto(@JsonProperty("id") Integer id, public EventDto(@JsonProperty("id") Integer id,
@ -36,7 +38,8 @@ public class EventDto {
@JsonProperty("updateDate") Date updateDate, @JsonProperty("updateDate") Date updateDate,
@JsonProperty("description") String description, @JsonProperty("description") String description,
@JsonProperty("paperDto") PaperDto paperDto, @JsonProperty("paperDto") PaperDto paperDto,
@JsonProperty("recipients") List<UserDto> recipients) { @JsonProperty("recipients") List<UserDto> recipients,
@JsonProperty("conferenceDto") ConferenceDto conferenceDto) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.period = period; this.period = period;
@ -47,6 +50,7 @@ public class EventDto {
this.description = description; this.description = description;
this.recipients = recipients; this.recipients = recipients;
this.paperDto = paperDto; this.paperDto = paperDto;
this.conferenceDto = conferenceDto;
} }
public EventDto(Event event) { public EventDto(Event event) {
@ -58,8 +62,13 @@ public class EventDto {
this.createDate = event.getCreateDate(); this.createDate = event.getCreateDate();
this.updateDate = event.getUpdateDate(); this.updateDate = event.getUpdateDate();
this.description = event.getDescription(); this.description = event.getDescription();
this.paperDto = new PaperDto(event.getPaper());
this.recipients = convert(event.getRecipients(), UserDto::new); this.recipients = convert(event.getRecipients(), UserDto::new);
if (paperDto != null) {
this.paperDto = new PaperDto(event.getPaper());
}
if (conferenceDto != null) {
this.conferenceDto = new ConferenceDto(event.getConference());
}
} }
public Integer getId() { public Integer getId() {
@ -105,4 +114,12 @@ public class EventDto {
public void setPaperDto(PaperDto paperDto) { public void setPaperDto(PaperDto paperDto) {
this.paperDto = paperDto; this.paperDto = paperDto;
} }
public ConferenceDto getConferenceDto() {
return conferenceDto;
}
public void setConferenceDto(ConferenceDto conferenceDto) {
this.conferenceDto = conferenceDto;
}
} }

View File

@ -2,6 +2,7 @@ package ru.ulstu.timeline.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import ru.ulstu.conference.model.Conference;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.timeline.model.Event; import ru.ulstu.timeline.model.Event;
@ -15,4 +16,6 @@ public interface EventRepository extends JpaRepository<Event, Integer> {
List<Event> findAllFuture(); List<Event> findAllFuture();
List<Event> findAllByPaper(Paper paper); List<Event> findAllByPaper(Paper paper);
List<Event> findAllByConference(Conference conference);
} }

View File

@ -4,6 +4,7 @@ import org.apache.commons.lang3.time.DateUtils;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.conference.model.Conference;
import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.model.Deadline;
import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.Paper;
import ru.ulstu.timeline.model.Event; import ru.ulstu.timeline.model.Event;
@ -140,4 +141,33 @@ public class EventService {
public List<EventDto> findAllFutureDto() { public List<EventDto> findAllFutureDto() {
return convert(findAllFuture(), EventDto::new); return convert(findAllFuture(), EventDto::new);
} }
public void createFromConference(Conference newConference) {
List<Timeline> timelines = timelineService.findAll();
Timeline timeline = timelines.isEmpty() ? new Timeline() : timelines.get(0);
for (Deadline deadline : newConference.getDeadlines()
.stream()
.filter(d -> d.getDate().after(new Date()) || DateUtils.isSameDay(d.getDate(), new Date()))
.collect(Collectors.toList())) {
Event newEvent = new Event();
newEvent.setTitle("Дедлайн конференции");
newEvent.setStatus(Event.EventStatus.NEW);
newEvent.setExecuteDate(deadline.getDate());
newEvent.setCreateDate(new Date());
newEvent.setUpdateDate(new Date());
newEvent.setDescription("Дедлайн '" + deadline.getDescription() + "' конференции '" + newConference.getTitle() + "'");
newConference.getUsers().forEach(conferenceUser -> newEvent.getRecipients().add(conferenceUser.getUser()));
newEvent.setConference(newConference);
save(newEvent);
timeline.getEvents().add(newEvent);
timelineService.save(timeline);
}
}
public void updateConferenceDeadlines(Conference conference) {
eventRepository.delete(eventRepository.findAllByConference(conference));
createFromConference(conference);
}
} }

View File

@ -0,0 +1,13 @@
<?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="vova" id="20190426_000000-1">
<addColumn tableName="event">
<column name="conference_id" type="integer"></column>
</addColumn>
<addForeignKeyConstraint baseTableName="event" baseColumnNames="conference_id"
constraintName="fk_event_conference_id" referencedTableName="conference"
referencedColumnNames="id"/>
</changeSet>
</databaseChangeLog>

View File

@ -34,5 +34,6 @@
<include file="db/changelog-20190421_000000-schema.xml"/> <include file="db/changelog-20190421_000000-schema.xml"/>
<include file="db/changelog-20190422_000000-schema.xml"/> <include file="db/changelog-20190422_000000-schema.xml"/>
<include file="db/changelog-20190424_000000-schema.xml"/> <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-20190428_000000-schema.xml"/>
</databaseChangeLog> </databaseChangeLog>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Уведомление о создании конференции</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Была создана новая конференция: "<a th:href="@{|${baseUrl}/conferences/conference?id=${conference.id}|}">
<span th:text="${conference.title}">Title</span></a>".
<br/>
Спешите принять участие!
</p>
<p>
Даты проведения:
<span th:text="${#dates.format(conference.beginDate, 'dd.MM.yyyy')}"></span>
-
<span th:text="${#dates.format(conference.endDate, 'dd.MM.yyyy')}"></span>.
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Уведомление о дедлайне конференции</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Приближается дедлайн конференции "<a th:href="@{|${baseUrl}/conferences/conference?id=${conference.id}|}">
<span th:text="${conference.title}">Title</span></a>".
</p>
<p>
Срок исполнения: <b><span th:text="${#dates.format(conference.nextDeadline.get().date, 'dd.MM.yyyy')}"></span></b>.
</p>
<p>
Примечание: <b><span th:text="${conference.nextDeadline.get().description}"></span></b>.
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Обратите внимание на конференциию</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Конференция "<a th:href="@{|${baseUrl}/conferences/conference?id=${conference.id}|}">
<span th:text="${conference.title}">Title</span></a>" была пропингована
<b><span th:text="${conference.ping}"></span></b> раз.
<br/>
Обратите внимание.
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Уведомление об изменении дат проведения конференции</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Даты проведения конференции "<a th:href="@{|${baseUrl}/conferences/conference?id=${conference.id}|}">
<span th:text="${conference.title}">Title</span></a>" изменились с <br/>
"<span th:text="${#dates.format(oldBeginDate, 'dd.MM.yyyy')}">oldBeginDate</span>"
-
"<span th:text="${#dates.format(oldEndDate, 'dd.MM.yyyy')}">oldEndDate</span>"
<br/>
на <br/>
"<span th:text="${#dates.format(conference.beginDate, 'dd.MM.yyyy')}"></span>"
-
"<span th:text="${#dates.format(conference.endDate, 'dd.MM.yyyy')}"></span>".
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Уведомление об изменении дедлайнов конференции</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head>
<body>
<p>
Уважаемый(ая) <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p>
<p>
Дедлайны конференции "<a th:href="@{|${baseUrl}/conferences/conference?id=${conference.id}|}">
<span th:text="${conference.title}">Title</span></a>" притерпели изменения.
<br/>
Ознакомтесь с изменениями.
</p>
<p>
Regards,
<br/>
<em>NG-tracker.</em>
</p>
</body>
</html>

View File

@ -7,6 +7,10 @@ body {
border-radius: .25rem; border-radius: .25rem;
} }
.conference-row .d-flex:hover .icon-delete {
visibility: visible;
}
.filter-option-inner-inner { .filter-option-inner-inner {
color: white; color: white;
} }
@ -17,10 +21,20 @@ body {
.conference-row .d-flex .text-decoration { .conference-row .d-flex .text-decoration {
text-decoration: none; text-decoration: none;
margin: 0;
} }
.conference-row .d-flex .text-decoration:nth-child(1) { .conference-row .d-flex .text-decoration:nth-child(1) {
margin-left: 10px; margin-left: 5px;
}
.conference-row .d-flex .icon-delete {
width: 29px;
height: 29px;
margin: auto;
border: none;
visibility: hidden;
background-color: transparent;
} }
@ -159,7 +173,7 @@ body {
} }
.icon-delete:hover { .icon-delete:hover {
background-color: #ff0000; background-color: #ff0000 !important;
transition: background-color .15s ease-in-out; transition: background-color .15s ease-in-out;
} }

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" <html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default" xmlns:th=""> layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html">
<head> <head>
<link rel="stylesheet" type="text/css" href="../css/conference.css"/> <link rel="stylesheet" type="text/css" href="../css/conference.css"/>
</head> </head>
@ -135,7 +135,6 @@
<div class="form-group"> <div class="form-group">
<label for="papers">Статьи:</label> <label for="papers">Статьи:</label>
<div class="paper-list form-control list-group" id="papers"> <div class="paper-list form-control list-group" id="papers">
<!--<input th:type="hidden" th:field="*{papers}"/>-->
<div class="paper d-flex list-group-item p-0" <div class="paper d-flex list-group-item p-0"
th:each="paper, rowStat : *{papers}"> th:each="paper, rowStat : *{papers}">
<input type="hidden" th:field="*{papers[__${rowStat.index}__].id}"/> <input type="hidden" th:field="*{papers[__${rowStat.index}__].id}"/>
@ -148,15 +147,13 @@
<span th:text="*{papers[__${rowStat.index}__].title}"> <span th:text="*{papers[__${rowStat.index}__].title}">
Имя статьи Имя статьи
</span> </span>
<!--<img class="icon-paper" src="/img/conference/paper.png"/>-->
</a> </a>
<a class="paper-name" <a class="paper-name"
th:unless="*{papers[__${rowStat.index}__].id !=null}"> th:unless="*{papers[__${rowStat.index}__].id !=null}">
<span th:replace="papers/fragments/paperStatusFragment :: paperStatus(paperStatus=*{papers[__${rowStat.index}__].status})"/>
<span th:text="*{papers[__${rowStat.index}__].title}"> <span th:text="*{papers[__${rowStat.index}__].title}">
Имя статьи Имя статьи
</span> </span>
<img class="icon-paper" src="/img/conference/paper.png"/>
</a> </a>
<input type="submit" class="icon icon-delete grey-border" <input type="submit" class="icon icon-delete grey-border"
alt="Удалить" name="removePaper" th:value="${rowStat.index}"/> alt="Удалить" name="removePaper" th:value="${rowStat.index}"/>

View File

@ -7,17 +7,13 @@
<div th:fragment="confLine (conference)" class="row text-left conference-row h3" style="background-color: white;"> <div th:fragment="confLine (conference)" class="row text-left conference-row h3" style="background-color: white;">
<div class="d-flex justify-content-between w-100"> <div class="d-flex justify-content-between w-100">
<a th:href="@{'conference?id='+${conference.id}}" class="w-100 text-decoration"> <a th:href="@{'conference?id='+${conference.id}}" class="w-100 text-decoration">
<span class="h5" th:text="${conference.title}"/> <span class="h6 float-left m-2" th:text="${conference.title}"/>
<span class="text-muted h6 float-right m-2" th:text="${conference.datesString}"/> <span class="text-muted h6 float-right m-2" th:text="${conference.datesString}"/>
</a> </a>
<input class="id-class" type="hidden" th:value="${conference.id}"/> <input class="id-class" type="hidden" th:value="${conference.id}"/>
<input type="submit" class="icon icon-delete grey-border" <input type="submit" class="icon icon-delete grey-border"
alt="Удалить" th:value="${conference.id}" alt="Удалить" th:value="${conference.id}"
data-confirm="Удалить конференцию?"/> data-confirm="Удалить конференцию?"/>
<!--<a class="remove-paper pull-right m-auto" th:href="@{'/conferences/delete/'+${conference.id}}"-->
<!--data-confirm="Удалить конференцию?">-->
<!--<i class="fa fa-trash" aria-hidden="true"></i>-->
<!--</a>-->
</div> </div>
</div> </div>