diff --git a/src/main/java/com/umc/timeto/block/controller/BlockController.java b/src/main/java/com/umc/timeto/block/controller/BlockController.java index b05a22b..6b6e555 100644 --- a/src/main/java/com/umc/timeto/block/controller/BlockController.java +++ b/src/main/java/com/umc/timeto/block/controller/BlockController.java @@ -15,7 +15,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.YearMonth; import java.util.List; @@ -77,24 +76,25 @@ public ResponseEntity>> getBlockNumByMonth } - @PatchMapping("/{blockId}/duration") - @Override - public ResponseEntity> updateDuration( - @PathVariable Long blockId, - @RequestParam LocalTime duration, - Authentication authentication - ) { - - blockService.updateBlockDuration( - blockId, - getMemberId(authentication), - duration - ); - - return ResponseEntity.ok( - new ResponseDTO<>(ResponseCode.SUCCESS_UPDATE_BLOCK, null) - ); - } +// @PatchMapping("/{blockId}/duration") +// @Override +// public ResponseEntity> updateDuration( +// @PathVariable Long blockId, +// @DateTimeFormat(pattern = "HH:mm:ss") +// @RequestParam LocalTime duration, +// Authentication authentication +// ) { +// +// blockService.updateBlockDuration( +// blockId, +// getMemberId(authentication), +// duration +// ); +// +// return ResponseEntity.ok( +// new ResponseDTO<>(ResponseCode.SUCCESS_UPDATE_BLOCK, null) +// ); +// } diff --git a/src/main/java/com/umc/timeto/block/controller/BlockControllerDocs.java b/src/main/java/com/umc/timeto/block/controller/BlockControllerDocs.java index 892fcd4..66b04e0 100644 --- a/src/main/java/com/umc/timeto/block/controller/BlockControllerDocs.java +++ b/src/main/java/com/umc/timeto/block/controller/BlockControllerDocs.java @@ -17,13 +17,15 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.YearMonth; import java.util.List; public interface BlockControllerDocs { @Operation(summary = "타임블럭 저장", description = "할 일 시작 시간을 받아서 블록을 저장합니다. 블록에는 일정 겹침 검사가 존재합니다." + - "(ex: 일정 1이 7:30~8:30 일떄, 일정2의 생성/이동은 8:30분 이상부터 가능)") + "(ex: 일정 1이 7:30~8:30 일떄, 일정2의 생성/이동은 8:30분 이상부터 가능)" + + "추가: startAt이 해당하는 날짜(당일 05시~다음날 05시 사이) 에 블록이 하나도 없을 경우에는 startAt의 값을 입력받아 생성됩니다." + + "하나라도 블록이 존재하면 가장 하단 블록의 endAt 시간대를 startAt으로 하여 생성됩니다. " + + "startAt의 날짜 부분에는 선택한 날짜를, 시간에는 현재 시간대나 default로 설정하고 싶은 시간을 입력해주세요. " ) @ApiResponses({ @ApiResponse( responseCode = "201", @@ -82,35 +84,35 @@ ResponseEntity>> getBlockNumByMonth( Authentication authentication ); - @Operation(summary = "블록 소요시간 변경", - description = "블록 소요시간 변경 시 사용합니다. 할 일 내부에서만 소요시간 변경이 가능하다면 사용하지 않아도 됩니다. ") - @ApiResponses({ - @ApiResponse( - responseCode = "200", - description = "블록을 성공적으로 업데이트했습니다" - ), - @ApiResponse( - responseCode = "404", - description = "해당 아이디를 가진 블록이 존재하지 않습니다.", - content = @Content(schema = @Schema(hidden = true)) - ), - @ApiResponse( - responseCode = "400", - description = "이미 해당 시간에 블록이 존재합니다." - ,content = @Content(schema = @Schema(hidden = true)) - ) - - }) - @PatchMapping("/{blockId}/duration") - ResponseEntity> updateDuration( - @PathVariable Long blockId, - @RequestParam LocalTime duration, - Authentication authentication - ); +// @Operation(summary = "블록 소요시간 변경", +// description = "블록 소요시간 변경 시 사용합니다. 할 일 내부에서만 소요시간 변경이 가능하다면 사용하지 않아도 됩니다. ") +// @ApiResponses({ +// @ApiResponse( +// responseCode = "200", +// description = "블록을 성공적으로 업데이트했습니다" +// ), +// @ApiResponse( +// responseCode = "404", +// description = "해당 아이디를 가진 블록이 존재하지 않습니다.", +// content = @Content(schema = @Schema(hidden = true)) +// ), +// @ApiResponse( +// responseCode = "400", +// description = "이미 해당 시간에 블록이 존재합니다." +// ,content = @Content(schema = @Schema(hidden = true)) +// ) +// +// }) +// @PatchMapping("/{blockId}/duration") +// ResponseEntity> updateDuration( +// @PathVariable Long blockId, +// @RequestParam LocalTime duration, +// Authentication authentication +// ); @Operation(summary = "블록 이동", description = "블록을 드래그&드롭으로 이동했을 때 정보를 갱신합니다. 변경된 시작 시간을 입력으로 받습니다. " + - "이동된 시간이 다른 일정과 겹칠 경우 갱신되지 않습니다. startAt format: yyyy-MM-dd'T'HH:mm") + "이동된 시간이 다른 일정과 겹칠 경우 갱신되지 않습니다. startAt format: yyyy-MM-dd'T'HH:mm(ex: 2026-02-14T:02:14) ") @ApiResponses({ @ApiResponse( responseCode = "200", diff --git a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java index b5e3fa9..89942db 100644 --- a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java +++ b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java @@ -2,6 +2,7 @@ import com.umc.timeto.block.entity.Block; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.time.LocalDateTime; import java.util.List; @@ -37,4 +38,20 @@ List findByTodo_Folder_Goal_Member_MemberIdAndBlockIdNotAndStartAtLessTha ); Optional findByBlockIdAndTodo_Folder_Goal_Member_MemberId(Long blockId, Long memberId); + + @Query(""" + select b + from Block b + join fetch b.todo t + join fetch t.folder f + join fetch f.goal g + where g.member.memberId = :memberId + and b.startAt between :start and :end +""") + List findBlocksWithTodo( + Long memberId, + LocalDateTime start, + LocalDateTime end + ); + } diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java index 35d5bc1..59afa09 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -18,6 +18,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.YearMonth; +import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -29,6 +30,7 @@ public class BlockServiceImpl implements BlockService { private final TodoRepository todoRepository; private final BlockRepository blockRepository; + private final BusinessDayPolicy businessDayPolicy; @Override public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) { @@ -36,29 +38,51 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) Todo todo = todoRepository.findByTodoIdAndFolder_Goal_Member_MemberId(todoId,memberId) .orElseThrow(() -> new GlobalException(ErrorCode.TODO_NOT_FOUND)); - - // 블록 겹침 조회 LocalDateTime startAt = req.getStartAt(); - LocalTime duration = todo.getDuration(); - LocalDateTime endAt = startAt - .plusHours(duration.getHour()) - .plusMinutes(duration.getMinute()) - .plusSeconds(duration.getSecond()); - System.out.println("시작시간" +startAt + "끝나는시간" +endAt); + // Todo당 Block 1개 보장 + blockRepository.findByTodo_TodoId(todoId) + .ifPresent(b -> { + throw new GlobalException(ErrorCode.BAD_REQUEST); + }); - List overlaps = + + // 05:00 기준 startAt에서 입력받은 날짜 범위 + LocalDateTime dayStart = businessDayPolicy.startOfBusinessDay(startAt); + LocalDateTime dayEnd = businessDayPolicy.endOfBusinessDay(startAt); + + + + List todayBlocks = blockRepository - .findByTodo_Folder_Goal_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( + .findByTodo_Folder_Goal_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( memberId, - endAt, - startAt + dayStart, + dayEnd ); - if (!overlaps.isEmpty()) { - throw new GlobalException(ErrorCode.BLOCK_TIME_CONFLICT); + + System.out.println("dayStart = " + dayStart); + System.out.println("dayEnd = " + dayEnd); + System.out.println("todayBlocks size = " + todayBlocks.size()); + + + + + if (todayBlocks.isEmpty()) { + // 오늘 블록 없으면 입력받은 시간에 생성 + startAt = req.getStartAt(); + } else { + // 가장 늦게 끝나는 블록 아래 배치 + Block lastBlock = todayBlocks.stream() + .max(Comparator.comparing(Block::getEndAt)) + .orElseThrow(); + + startAt = lastBlock.getEndAt(); } + //생성 시 시작시간 추가되도록 + todo.updateStartAt(startAt); //블록 저장 Block block = new Block(todo, startAt); @@ -76,16 +100,17 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) @Override public List getBlockByDay(LocalDate date, Long memberId) { - LocalDateTime start = date.atStartOfDay(); - LocalDateTime end = date.atTime(23, 59, 59); + LocalDateTime time_standard = date.atTime(5, 0); + + LocalDateTime start = businessDayPolicy.startOfBusinessDay(time_standard); + LocalDateTime end = businessDayPolicy.endOfBusinessDay(time_standard); List blocks = - blockRepository - .findByTodo_Folder_Goal_Member_MemberIdAndStartAtBetween( - memberId, - start, - end - ); + blockRepository.findBlocksWithTodo( + memberId, + start, + end + ); return blocks.stream() diff --git a/src/main/java/com/umc/timeto/block/service/BusinessDayPolicy.java b/src/main/java/com/umc/timeto/block/service/BusinessDayPolicy.java new file mode 100644 index 0000000..386fa97 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/service/BusinessDayPolicy.java @@ -0,0 +1,28 @@ +package com.umc.timeto.block.service; + +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Component +public class BusinessDayPolicy { + + private static final LocalTime DAY_START = LocalTime.of(5, 0); + + public LocalDateTime startOfBusinessDay(LocalDateTime base) { + LocalDate date = base.toLocalDate(); + + if (base.toLocalTime().isBefore(DAY_START)) { + date = date.minusDays(1); + } + + return date.atTime(DAY_START); + } + + public LocalDateTime endOfBusinessDay(LocalDateTime base) { + return startOfBusinessDay(base).plusDays(1); + } +} + diff --git a/src/main/java/com/umc/timeto/todo/domain/Todo.java b/src/main/java/com/umc/timeto/todo/domain/Todo.java index d7a138e..fb236b7 100644 --- a/src/main/java/com/umc/timeto/todo/domain/Todo.java +++ b/src/main/java/com/umc/timeto/todo/domain/Todo.java @@ -9,6 +9,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicUpdate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -16,6 +17,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@DynamicUpdate @Table(name = "todo") public class Todo { @Id