Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 공간 조건 적용 사용성 개선 #963

Merged
merged 16 commits into from
Apr 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions backend/src/docs/asciidoc/setting.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
== Setting(공간 예약 조건)

=== 특정 날짜 예약 조건 조회 (예약자 view 용 = flat)
==== Request
include::{snippets}/setting/get_flat/http-request.adoc[]
==== Response
include::{snippets}/setting/get_flat/http-response.adoc[]

=== 특정 날짜 예약 조건 조회 (관리자 view 용 = stack)
==== 공간 관리자
===== Request
include::{snippets}/setting/get_stack/http-request.adoc[]
===== Response
include::{snippets}/setting/get_stack/http-response.adoc[]

=== 전체 예약 조건 조회 (예약자 view 용 = flat)
==== Request
include::{snippets}/setting/get_flat_all/http-request.adoc[]
==== Response
include::{snippets}/setting/get_flat_all/http-response.adoc[]

=== 전체 예약 조건 조회 (관리자 view 용 = stack)
==== 공간 관리자
===== Request
include::{snippets}/setting/get_stack_all/http-request.adoc[]
===== Response
include::{snippets}/setting/get_stack_all/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.woowacourse.zzimkkong.controller;

import com.woowacourse.zzimkkong.config.logaspect.LogMethodExecutionTime;
import com.woowacourse.zzimkkong.dto.space.SettingsSummaryResponse;
import com.woowacourse.zzimkkong.domain.SettingViewType;
import com.woowacourse.zzimkkong.infrastructure.datetime.TimeZoneUtils;
import com.woowacourse.zzimkkong.service.SettingService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.ZonedDateTime;

import static com.woowacourse.zzimkkong.dto.ValidatorMessage.DATETIME_FORMAT;

@LogMethodExecutionTime(group = "controller")
@RestController
@RequestMapping("/api/maps/{mapId}/spaces/{spaceId}/settings")
public class SettingController {
private final SettingService settingService;

public SettingController(final SettingService settingService) {
this.settingService = settingService;
}

@GetMapping("/summary")
public ResponseEntity<SettingsSummaryResponse> getSettingsSummary(
@PathVariable final Long mapId,
@PathVariable final Long spaceId,
@RequestParam(required = false) @DateTimeFormat(pattern = DATETIME_FORMAT) final ZonedDateTime selectedDateTime,
@RequestParam(required = false, defaultValue = "FLAT") final String settingViewType) {
SettingsSummaryResponse settingsSummaryResponse = settingService.getSettingsSummary(
mapId,
spaceId,
TimeZoneUtils.convertToUTC(selectedDateTime),
settingViewType);
return ResponseEntity.ok().body(settingsSummaryResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package com.woowacourse.zzimkkong.domain;

import com.woowacourse.zzimkkong.exception.setting.NoSuchEnabledDayOfWeekException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;

@Slf4j
@Getter
public enum EnabledDayOfWeek {
MONDAY(""),
TUESDAY(""),
WEDNESDAY(""),
THURSDAY(""),
FRIDAY(""),
SATURDAY(""),
SUNDAY("");
MONDAY("월요일"),
TUESDAY("화요일"),
WEDNESDAY("수요일"),
THURSDAY("목요일"),
FRIDAY("금요일"),
SATURDAY("토요일"),
SUNDAY("일요일");

private final String displayName;

Expand Down
173 changes: 163 additions & 10 deletions backend/src/main/java/com/woowacourse/zzimkkong/domain/Setting.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
package com.woowacourse.zzimkkong.domain;

import com.woowacourse.zzimkkong.exception.setting.InvalidOrderException;
import com.woowacourse.zzimkkong.exception.space.InvalidMinimumMaximumTimeUnitException;
import com.woowacourse.zzimkkong.exception.space.NotEnoughAvailableTimeException;
import com.woowacourse.zzimkkong.exception.space.TimeUnitInconsistencyException;
import com.woowacourse.zzimkkong.exception.space.TimeUnitMismatchException;
import lombok.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.logstash.logback.encoder.org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import static com.woowacourse.zzimkkong.dto.ValidatorMessage.INVALID_SETTING_ORDER_MESSAGE;
import static com.woowacourse.zzimkkong.infrastructure.message.MessageUtils.LINE_SEPARATOR;

@Builder
@Getter
@NoArgsConstructor
@Entity
public class Setting {
public static final int FLAT_PRIORITY_ORDER = -1;
public static final long FLAT_SETTING_ID = 0L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand Down Expand Up @@ -57,6 +68,9 @@ public class Setting {
@Column(nullable = false)
private String enabledDayOfWeek;

@Column(nullable = false)
private Integer priorityOrder;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "space_id", foreignKey = @ForeignKey(name = "fk_setting_space"), nullable = false)
private Space space;
Expand All @@ -68,18 +82,32 @@ public Setting(
final TimeUnit reservationMinimumTimeUnit,
final TimeUnit reservationMaximumTimeUnit,
final String enabledDayOfWeek,
final Integer priorityOrder,
final Space space) {
this.id = id;
this.settingTimeSlot = settingTimeSlot;
this.reservationTimeUnit = reservationTimeUnit;
this.reservationMinimumTimeUnit = reservationMinimumTimeUnit;
this.reservationMaximumTimeUnit = reservationMaximumTimeUnit;
this.enabledDayOfWeek = enabledDayOfWeek;
this.priorityOrder = priorityOrder;
this.space = space;

validateSetting();
}

public Setting createSettingBasedOn(final TimeSlot timeSlot, final EnabledDayOfWeek dayOfWeek) {
return Setting.builder()
.id(this.getId())
.settingTimeSlot(timeSlot)
.reservationTimeUnit(this.getReservationTimeUnit())
.reservationMinimumTimeUnit(this.getReservationMinimumTimeUnit())
.reservationMaximumTimeUnit(this.getReservationMaximumTimeUnit())
.enabledDayOfWeek(dayOfWeek.name().toLowerCase(Locale.ROOT))
.priorityOrder(this.getPriorityOrder())
.build();
}

private void validateSetting() {
if (settingTimeSlot.isNotDivisibleBy(reservationTimeUnit)) {
throw new TimeUnitMismatchException();
Expand All @@ -96,6 +124,10 @@ private void validateSetting() {
if (settingTimeSlot.isDurationShorterThan(reservationMaximumTimeUnit)) {
throw new NotEnoughAvailableTimeException();
}

if (priorityOrder == null || priorityOrder < FLAT_PRIORITY_ORDER) {
throw new InvalidOrderException(INVALID_SETTING_ORDER_MESSAGE);
}
}

public LocalTime getSettingStartTime() {
Expand Down Expand Up @@ -124,14 +156,8 @@ private boolean isNotConsistentTimeUnit() {
}

public boolean hasConflictWith(final Setting that) {
List<EnabledDayOfWeek> thisEnabledDayOfWeek = Arrays.stream(this.enabledDayOfWeek.split(Space.DELIMITER))
.map(String::trim)
.map(EnabledDayOfWeek::from)
.collect(Collectors.toList());
boolean enabledDayOfWeekMatch = Arrays.stream(that.enabledDayOfWeek.split(Space.DELIMITER))
.map(String::trim)
.map(EnabledDayOfWeek::from)
.anyMatch(thisEnabledDayOfWeek::contains);
List<EnabledDayOfWeek> thisEnabledDayOfWeek = this.getEnabledDayOfWeekList();
boolean enabledDayOfWeekMatch = that.getEnabledDayOfWeekList().stream().anyMatch(thisEnabledDayOfWeek::contains);

return this.settingTimeSlot.hasConflictWith(that.settingTimeSlot) && enabledDayOfWeekMatch;
}
Expand All @@ -157,9 +183,136 @@ public void updateSpace(final Space space) {
this.space = space;
}

public List<EnabledDayOfWeek> getEnabledDayOfWeekList() {
return Arrays.stream(this.enabledDayOfWeek.split(Space.DELIMITER))
.map(String::trim)
.map(EnabledDayOfWeek::from)
.collect(Collectors.toList());
}

/**
* 2023.04.02 기준
* 인자로 주어진 settings 의 조건들에 배타적인 (겹치지 않는) 새로운 setting slot 리스트를 생성한다
* 기존 setting 을 조각내어 새로운 여러개의 (transient) setting 을 생성한다
*
* @param settings 서로 시간대와 요일이 겹치지 않는 (= flat 한) setting 들
* @return List of Setting Slot (조각 내어진 세팅 슬롯들)
*/

public List<Setting> extractExclusiveSettingSlots(final List<Setting> settings) {
List<Setting> exclusiveSettingSlots = List.of(Setting.builder()
.id(FLAT_SETTING_ID)
.settingTimeSlot(this.settingTimeSlot)
.reservationTimeUnit(this.reservationTimeUnit)
.reservationMinimumTimeUnit(this.reservationMinimumTimeUnit)
.reservationMaximumTimeUnit(this.reservationMaximumTimeUnit)
.enabledDayOfWeek(this.enabledDayOfWeek)
.priorityOrder(FLAT_PRIORITY_ORDER)
.space(this.space)
.build());
for (Setting setting : settings) {
List<Setting> newExclusiveSettingSlots = new ArrayList<>();
for (Setting exclusiveSettingSlot : exclusiveSettingSlots) {
newExclusiveSettingSlots.addAll(exclusiveSettingSlot.extractNewExclusiveSettingSlots(setting));
}
exclusiveSettingSlots = newExclusiveSettingSlots;
}
return exclusiveSettingSlots;
}

private List<Setting> extractNewExclusiveSettingSlots(final Setting setting) {
if (!this.hasConflictWith(setting)) {
return List.of(this);
}

List<Setting> newExclusiveSettingSlots = new ArrayList<>();

List<TimeSlot> exclusiveTimeSlots = this.settingTimeSlot.extractExclusiveTimeSlots(setting.settingTimeSlot);
for (TimeSlot exclusiveTimeSlot : exclusiveTimeSlots) {
TimeUnit adjustedIntervalTimeUnit = this.reservationTimeUnit.getAdjustedIntervalTimeUnit(exclusiveTimeSlot);

Setting survivedSettingSlot = Setting.builder()
.id(FLAT_SETTING_ID)
.settingTimeSlot(exclusiveTimeSlot)
.reservationTimeUnit(adjustedIntervalTimeUnit)
.reservationMinimumTimeUnit(
this.reservationMinimumTimeUnit.getAdjustedTimeUnit(
exclusiveTimeSlot,
adjustedIntervalTimeUnit))
.reservationMaximumTimeUnit(
this.reservationMaximumTimeUnit.getAdjustedTimeUnit(
exclusiveTimeSlot,
adjustedIntervalTimeUnit))
.enabledDayOfWeek(this.enabledDayOfWeek)
.priorityOrder(FLAT_PRIORITY_ORDER)
.space(this.space)
.build();

newExclusiveSettingSlots.add(survivedSettingSlot);
}

List<EnabledDayOfWeek> conflictingSettingEnabledDayOfWeek = setting.getEnabledDayOfWeekList();
List<EnabledDayOfWeek> exclusiveEnabledDayOfWeek = this.getEnabledDayOfWeekList()
.stream()
.filter(dayOfWeek -> !conflictingSettingEnabledDayOfWeek.contains(dayOfWeek))
.collect(Collectors.toList());

if (!CollectionUtils.isEmpty(exclusiveEnabledDayOfWeek)) {
TimeSlot overlappingTimeSlot = this.settingTimeSlot.extractOverlappingTimeSlot(setting.settingTimeSlot);
String nonConflictingDayOfWeek = exclusiveEnabledDayOfWeek.stream()
.map(dayOfWeek -> dayOfWeek.name().toLowerCase(Locale.ROOT))
.collect(Collectors.joining(","));
TimeUnit adjustedIntervalTimeUnit = this.reservationTimeUnit.getAdjustedIntervalTimeUnit(overlappingTimeSlot);

Setting survivedSettingSlot = Setting.builder()
.id(FLAT_SETTING_ID)
.settingTimeSlot(overlappingTimeSlot)
.reservationTimeUnit(adjustedIntervalTimeUnit)
.reservationMinimumTimeUnit(
this.reservationMinimumTimeUnit.getAdjustedTimeUnit(
overlappingTimeSlot,
adjustedIntervalTimeUnit))
.reservationMaximumTimeUnit(
this.reservationMaximumTimeUnit.getAdjustedTimeUnit(
overlappingTimeSlot,
adjustedIntervalTimeUnit))
.enabledDayOfWeek(nonConflictingDayOfWeek)
.priorityOrder(FLAT_PRIORITY_ORDER)
.space(this.space)
.build();
newExclusiveSettingSlots.add(survivedSettingSlot);
}

return newExclusiveSettingSlots;
}

public String toSummaryWithoutDayOfWeek(final Boolean flat) {
String priority = "[우선순위 " + priorityOrder.toString() + "] ";
if (flat) {
priority = StringUtils.EMPTY;
}
return String.format("%s%s (최소 %s, 최대 %s, 예약 단위 %s)",
priority,
settingTimeSlot.toString(),
reservationMinimumTimeUnit.toString(),
reservationMaximumTimeUnit.toString(),
reservationTimeUnit.toString());
}

public boolean canMergeIgnoringDayOfWeek(final Setting that) {
return this.settingTimeSlot.isExtendableWith(that.settingTimeSlot)
&& this.reservationTimeUnit.equals(that.reservationTimeUnit)
&& this.reservationMinimumTimeUnit.equals(that.reservationMinimumTimeUnit)
&& this.reservationMaximumTimeUnit.equals(that.reservationMaximumTimeUnit);
}

public boolean isFlattenedSetting() {
return FLAT_PRIORITY_ORDER == this.priorityOrder;
}

@Override
public String toString() {
return "예약 가능한 요일: " +
return "예약 요일: " +
EnabledDayOfWeek.getDisplayNames(enabledDayOfWeek) +
LINE_SEPARATOR +
"예약 가능한 시간대: " +
Expand Down
Loading