diff --git a/src/main/java/com/coderder/colorMeeting/controller/ScheduleController.java b/src/main/java/com/coderder/colorMeeting/controller/ScheduleController.java index 30221e1..246aeb3 100644 --- a/src/main/java/com/coderder/colorMeeting/controller/ScheduleController.java +++ b/src/main/java/com/coderder/colorMeeting/controller/ScheduleController.java @@ -3,7 +3,6 @@ import com.coderder.colorMeeting.config.auth.PrincipalDetails; import com.coderder.colorMeeting.dto.request.ScheduleRequestDto; import com.coderder.colorMeeting.dto.request.TeamScheduleRequestDto; -import com.coderder.colorMeeting.dto.request.TeamTimeDto; import com.coderder.colorMeeting.dto.response.*; import com.coderder.colorMeeting.service.ScheduleService; import org.springframework.beans.factory.annotation.Autowired; @@ -65,11 +64,11 @@ public ResponseEntity> getTeamUserAllSchedule(@Req return ResponseEntity.ok().body(scheduleBlockDtoList); } @GetMapping("/api/schedule/recommendations") - public ResponseEntity getRecommendations(@RequestBody TeamTimeDto teamTimeDto){ - List recommendationList = scheduleService.getTeamEmptyTimes(teamTimeDto); - ScheduleListDto scheduleListDto = ScheduleListDto.builder() - .schedule(recommendationList).build(); - return ResponseEntity.ok().body(scheduleListDto); + public ResponseEntity getRecommendations( + @RequestParam Long teamId, + @RequestParam Integer spanTime){ + RandomRecommendationDto randomRecommendationDto = scheduleService.getRandomRecommandation(teamId, spanTime); + return ResponseEntity.ok().body(randomRecommendationDto); } @PostMapping("/api/schedule/teamschedule") public ResponseEntity makeTeamSchedule(@RequestParam Long teamId, @RequestBody TeamScheduleRequestDto teamScheduleDto){ diff --git a/src/main/java/com/coderder/colorMeeting/dto/request/TeamTimeDto.java b/src/main/java/com/coderder/colorMeeting/dto/request/TeamTimeDto.java index a79aff5..5657a82 100644 --- a/src/main/java/com/coderder/colorMeeting/dto/request/TeamTimeDto.java +++ b/src/main/java/com/coderder/colorMeeting/dto/request/TeamTimeDto.java @@ -1,8 +1,11 @@ package com.coderder.colorMeeting.dto.request; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.Getter; @Getter +@AllArgsConstructor public class TeamTimeDto { private Long teamId; private int spendingMinute; diff --git a/src/main/java/com/coderder/colorMeeting/dto/response/AvailableScheduleDto.java b/src/main/java/com/coderder/colorMeeting/dto/response/AvailableScheduleDto.java new file mode 100644 index 0000000..6f9a0a0 --- /dev/null +++ b/src/main/java/com/coderder/colorMeeting/dto/response/AvailableScheduleDto.java @@ -0,0 +1,14 @@ +package com.coderder.colorMeeting.dto.response; + +import com.coderder.colorMeeting.model.Member; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import net.bytebuddy.implementation.bind.annotation.Super; + +import java.util.List; + +@SuperBuilder +@Getter +public class AvailableScheduleDto extends ScheduleBlockDto{ + private List availableMember; +} diff --git a/src/main/java/com/coderder/colorMeeting/dto/response/RandomRecommendationDto.java b/src/main/java/com/coderder/colorMeeting/dto/response/RandomRecommendationDto.java new file mode 100644 index 0000000..72370bf --- /dev/null +++ b/src/main/java/com/coderder/colorMeeting/dto/response/RandomRecommendationDto.java @@ -0,0 +1,14 @@ +package com.coderder.colorMeeting.dto.response; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class RandomRecommendationDto { + private String start; + private String end; + private List memberNickNames; +} diff --git a/src/main/java/com/coderder/colorMeeting/etc/ScheduleCalendar.java b/src/main/java/com/coderder/colorMeeting/etc/ScheduleCalendar.java new file mode 100644 index 0000000..f2c857f --- /dev/null +++ b/src/main/java/com/coderder/colorMeeting/etc/ScheduleCalendar.java @@ -0,0 +1,157 @@ +package com.coderder.colorMeeting.etc; + +import com.coderder.colorMeeting.dto.response.AvailableScheduleDto; +import com.coderder.colorMeeting.model.Member; +import com.coderder.colorMeeting.model.PersonalSchedule; +import lombok.AllArgsConstructor; + +import java.time.LocalTime; +import java.util.*; + +@AllArgsConstructor +public class ScheduleCalendar { + private List>> calendar; + private List teamMembers; + + public static ScheduleCalendar createCalendar(List teamSchedules, List members, int hourDividedBy) { + List>> calendar = new ArrayList<>(); + for(int i=0; i<7; i++){ + List> list = new ArrayList<>(); + for(int j=0; j<24*hourDividedBy; j++){ + list.add(new HashSet<>()); + } + calendar.add(list); + } + + for(PersonalSchedule schedule : teamSchedules){ + List> list = calendar.get(convertWeekday(schedule.getWeekday())); + int startIndex = convertTime(schedule.getStartTime(), hourDividedBy); + int endIndex = convertTime(schedule.getFinishTime().minusMinutes(1), hourDividedBy); + for(int i=startIndex; i<=endIndex; i++){ + list.get(i).add(schedule.getMember()); + } + } + //members can change!!! + return new ScheduleCalendar(calendar, members); + } + private static int convertWeekday(String weekday){ + if(weekday.equalsIgnoreCase("mon") || weekday.equalsIgnoreCase("monday")) return 0; + else if(weekday.equalsIgnoreCase("tue") || weekday.equalsIgnoreCase("tuesday")) return 1; + else if(weekday.equalsIgnoreCase("wed") || weekday.equalsIgnoreCase("wednesday")) return 2; + else if(weekday.equalsIgnoreCase("thu") || weekday.equalsIgnoreCase("thursday")) return 3; + else if(weekday.equalsIgnoreCase("fri") || weekday.equalsIgnoreCase("friday")) return 4; + else if(weekday.equalsIgnoreCase("sat") || weekday.equalsIgnoreCase("saturday")) return 5; + else if(weekday.equalsIgnoreCase("sun") || weekday.equalsIgnoreCase("sunday")) return 6; + else return -1; + } + private static int convertTime(LocalTime time, int timeInterval){ + int min = time.getMinute(); + int hour = time.getHour(); + int blockIndex = hour*timeInterval + (min / (60/timeInterval)); + return blockIndex; + } + + private static int convertTime(String time, int timeInterval){ + int min = Integer.parseInt(time.split(":")[1]); + int hour = Integer.parseInt(time.split(":")[0]); + int blockIndex = hour*timeInterval + (min / (60/timeInterval)); + return blockIndex; + } + + private String convertToWeekday(int weekday){ + if(weekday == 0) return "mon"; + else if(weekday == 1) return "tue"; + else if(weekday == 2) return "wed"; + else if(weekday == 3) return "thu"; + else if(weekday == 4) return "fri"; + else if(weekday == 5) return "sat"; + else if(weekday == 6) return "sun"; + else return ""; + } + private LocalTime convertToTime(int timeBlock, int timeInterval){ + return LocalTime.of(timeBlock/timeInterval + , (timeBlock%timeInterval)*(60/timeInterval)); + } + + + public List getMostAvailableList(Integer spanTime, Integer hourDividedBy) { + Integer maxNum = 0; + Integer requiredBlockNum = spanTime / (60/hourDividedBy); + System.out.println(requiredBlockNum); + //then, start,end time would be start only by "hourDividedBy" + + //count continuous blocks(value) per member(key) + Map memberCounter = new HashMap<>(); + + initializeCounter(memberCounter, teamMembers); + List availableScheduleDtoList = new ArrayList<>(); + for(int i=0; i< calendar.size(); i++){ + List> day = calendar.get(i); + for(int blockIndex=0; blockIndex oneBlock = day.get(blockIndex); + setCounter(memberCounter, oneBlock, requiredBlockNum); + + Set availableMems = new HashSet<>(); + for(Member member : memberCounter.keySet()){ + if(memberCounter.get(member) == requiredBlockNum) availableMems.add(member); + } + + if(maxNum < availableMems.size()){ + maxNum = availableMems.size(); + availableScheduleDtoList.clear(); + String start = convertBlockToTime(blockIndex-requiredBlockNum+1, hourDividedBy); + String end = convertBlockToTime(blockIndex, hourDividedBy); + insertAvailableSchedule(availableScheduleDtoList, availableMems, start, end, convertToWeekday(i)); + + }else if(maxNum == availableMems.size()){ + String start = convertBlockToTime(blockIndex-requiredBlockNum+1, hourDividedBy); + String end = convertBlockToTime(blockIndex+1, hourDividedBy); + insertAvailableSchedule(availableScheduleDtoList, availableMems, start, end, convertToWeekday(i)); + } + } + } + return availableScheduleDtoList; + } + + private String convertBlockToTime(Integer blockIndex, Integer hourDividedBy) { + int hour = blockIndex / hourDividedBy; + int minute = (blockIndex % hourDividedBy) * (60/hourDividedBy); + + String time = String.format("%02d",hour)+":"+String.format("%02d",minute); + return time; + } + + private void setCounter(Map memberCounter, Set oneBlock, Integer requiredBlockNum) { + for(Member mem : memberCounter.keySet()){ + if(oneBlock.contains(mem)) memberCounter.put(mem, 0); + else { + Integer continuousCount = memberCounter.get(mem) == requiredBlockNum ? + memberCounter.get(mem) : memberCounter.get(mem)+1; + memberCounter.put(mem, continuousCount); + } + } + } + + private void insertAvailableSchedule(List availableScheduleDtoList, + Set availableMems, + String start, + String end, + String weekday) { + List templist = new ArrayList<>(); + for(Member mem : availableMems){ + templist.add(mem); + } + AvailableScheduleDto dto = AvailableScheduleDto.builder() + .availableMember(templist) + .start(weekday+"+"+start) + .end(weekday+"+"+end) + .build(); + availableScheduleDtoList.add(dto); + } + + private void initializeCounter(Map memberCounter, List teamMembers) { + for(Member member : teamMembers){ + memberCounter.put(member, 0); + } + } +} diff --git a/src/main/java/com/coderder/colorMeeting/service/ScheduleService.java b/src/main/java/com/coderder/colorMeeting/service/ScheduleService.java index d1269ef..cf8497d 100644 --- a/src/main/java/com/coderder/colorMeeting/service/ScheduleService.java +++ b/src/main/java/com/coderder/colorMeeting/service/ScheduleService.java @@ -3,10 +3,7 @@ import com.coderder.colorMeeting.dto.request.ScheduleRequestDto; import com.coderder.colorMeeting.dto.request.TeamScheduleRequestDto; import com.coderder.colorMeeting.dto.request.TeamTimeDto; -import com.coderder.colorMeeting.dto.response.PersonalScheduleDto; -import com.coderder.colorMeeting.dto.response.PersonalScheduleListDto; -import com.coderder.colorMeeting.dto.response.ScheduleBlockDto; -import com.coderder.colorMeeting.dto.response.TeamScheduleDto; +import com.coderder.colorMeeting.dto.response.*; import com.coderder.colorMeeting.model.Member; import java.util.List; @@ -19,7 +16,6 @@ public interface ScheduleService { List getBlockListByTeamIdWithoutOverlap(Long teamId); - List getTeamEmptyTimes(TeamTimeDto teamTimeDto); void insertGroupSchedule(TeamScheduleRequestDto teamScheduleDto); @@ -35,4 +31,6 @@ public interface ScheduleService { void updateGroupSchedule(TeamScheduleRequestDto teamScheduleDto); void deleteGroupSchedule(Long scheduleId); + + RandomRecommendationDto getRandomRecommandation(Long teamId, Integer spanTime); } diff --git a/src/main/java/com/coderder/colorMeeting/service/ScheduleServiceImpl.java b/src/main/java/com/coderder/colorMeeting/service/ScheduleServiceImpl.java index 1b48614..e0651e6 100644 --- a/src/main/java/com/coderder/colorMeeting/service/ScheduleServiceImpl.java +++ b/src/main/java/com/coderder/colorMeeting/service/ScheduleServiceImpl.java @@ -4,6 +4,7 @@ import com.coderder.colorMeeting.dto.request.TeamScheduleRequestDto; import com.coderder.colorMeeting.dto.request.TeamTimeDto; import com.coderder.colorMeeting.dto.response.*; +import com.coderder.colorMeeting.etc.ScheduleCalendar; import com.coderder.colorMeeting.exception.ErrorCode; import com.coderder.colorMeeting.exception.NotFoundException; import com.coderder.colorMeeting.model.Member; @@ -16,10 +17,8 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; @Service public class ScheduleServiceImpl implements ScheduleService{ @@ -123,7 +122,6 @@ private List calendarToScheduleDto(boolean[][] calendar) { return scheduleBlockDtoList; } - @Override public List getTeamEmptyTimes(TeamTimeDto teamTimeDto) { List members = memberRepository.findAllWithTeamId(teamTimeDto.getTeamId()); List teamSchedules = personalScheduleRepository.findAllByMemberIn(members); @@ -325,4 +323,39 @@ public void updateGroupSchedule(TeamScheduleRequestDto teamScheduleDto) { public void deleteGroupSchedule(Long scheduleId) { teamScheduleRepository.deleteById(scheduleId); } + + @Override + public RandomRecommendationDto getRandomRecommandation(Long teamId, Integer spanTime) { + List teamEmptyTimes = getMaxAvailableList(teamId, spanTime); + + Random random = new Random(); + AvailableScheduleDto randomSchedule = teamEmptyTimes.get(random.nextInt(teamEmptyTimes.size())); + + List availableMembers = randomSchedule.getAvailableMember() + .stream() + .map(Member::getNickname) + .collect(Collectors.toList()); + + RandomRecommendationDto randomRecommendationDto = RandomRecommendationDto.builder() + .start(randomSchedule.getStart()) + .end(randomSchedule.getEnd()) + .memberNickNames(availableMembers) + .build(); + + return randomRecommendationDto; + } + + private List getMaxAvailableList(Long teamId, Integer spanTime) { + List members = memberRepository.findAllWithTeamId(teamId); + List teamSchedules = personalScheduleRepository.findAllByMemberIn(members); + + List recommendationDtos = getMostEmptyTime(teamSchedules, members, spanTime, 2); + return recommendationDtos; + } + + private List getMostEmptyTime(List teamSchedules, List members, Integer spanTime, int hourDividedBy) { + ScheduleCalendar calendar = ScheduleCalendar.createCalendar(teamSchedules, members, hourDividedBy); + List mostAvailableList = calendar.getMostAvailableList(spanTime, hourDividedBy); + return mostAvailableList; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8f8d0ec..045f93a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.profiles.include=db-username,db-url,db-password,secret # DB ddl -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=none # SQL DB spring.jpa.properties.hibernate.format_sql=true