Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.eatsfine.eatsfine.domain.businesshours.controller;

import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursReqDto;
import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursResDto;
import com.eatsfine.eatsfine.domain.businesshours.service.BusinessHoursCommandService;
import com.eatsfine.eatsfine.domain.businesshours.status.BusinessHoursSuccessStatus;
import com.eatsfine.eatsfine.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@Tag(name = "BusinessHours", description = "영업시간 관련 API")
@RequestMapping("/api/v1")
@RestController
@RequiredArgsConstructor
public class BusinessHoursController {

private final BusinessHoursCommandService businessHoursCommandService;

@Operation(
summary = "가게 영업시간 수정",
description = "가게의 영업시간을 수정합니다."
)
@PatchMapping("/stores/{storeId}/business-hours")
public ApiResponse<BusinessHoursResDto.UpdateBusinessHoursDto> updateBusinessHours(
@PathVariable Long storeId,
@RequestBody BusinessHoursReqDto.UpdateBusinessHoursDto dto
){
return ApiResponse.of(
BusinessHoursSuccessStatus._UPDATE_BUSINESS_HOURS_SUCCESS,
businessHoursCommandService.updateBusinessHours(storeId, dto)
);
}

@Operation(
summary = "브레이크타임 설정",
description = "가게의 브레이크타임을 설정합니다."
)
@PatchMapping("/stores/{storeId}/break-time")
public ApiResponse<BusinessHoursResDto.UpdateBreakTimeDto> updateBreakTime(
@PathVariable Long storeId,
@RequestBody BusinessHoursReqDto.UpdateBreakTimeDto dto
){
return ApiResponse.of(
BusinessHoursSuccessStatus._UPDATE_BREAKTIME_SUCCESS,
businessHoursCommandService.updateBreakTime(storeId, dto)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@
import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursResDto;
import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours;

import java.util.List;

public class BusinessHoursConverter {

public static BusinessHours toEntity(BusinessHoursReqDto.Summary dto) {
return BusinessHours.builder()
.dayOfWeek(dto.dayOfWeek())
.dayOfWeek(dto.day())
.openTime(dto.openTime())
.closeTime(dto.closeTime())
.isHoliday(dto.isClosed()) // 특정 요일 고정 휴무
.isClosed(dto.isClosed()) // 특정 요일 고정 휴무
.build();
}



public static BusinessHoursResDto.Summary toSummary(BusinessHours bh) {
// 휴무일 때
if(bh.isHoliday()) {
if(bh.isClosed()) {
return BusinessHoursResDto.Summary.builder()
.day(bh.getDayOfWeek())
.openTime(null)
.closeTime(null)
.isClosed(true)
.build();
}
Expand All @@ -33,4 +37,23 @@ public static BusinessHoursResDto.Summary toSummary(BusinessHours bh) {
.isClosed(false)
.build();
}

public static BusinessHoursResDto.UpdateBusinessHoursDto toUpdateBusinessHoursDto(Long storeId, List<BusinessHours> updatedBusinessHours) {
return BusinessHoursResDto.UpdateBusinessHoursDto.builder()
.storeId(storeId)
.updatedBusinessHours(
updatedBusinessHours.stream().map(
BusinessHoursConverter::toSummary
).toList()
)
.build();
}

public static BusinessHoursResDto.UpdateBreakTimeDto toUpdateBreakTimeDto(Long storeId, BusinessHoursReqDto.UpdateBreakTimeDto dto) {
return BusinessHoursResDto.UpdateBreakTimeDto.builder()
.storeId(storeId)
.breakStartTime(dto.breakStartTime())
.breakEndTime(dto.breakEndTime())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package com.eatsfine.eatsfine.domain.businesshours.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.List;

public class BusinessHoursReqDto {

@Builder
public record Summary(

@NotNull(message = "요일은 필수입니다.")
DayOfWeek dayOfWeek,
DayOfWeek day,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
LocalTime openTime,
Expand All @@ -23,4 +25,22 @@ public record Summary(

boolean isClosed
){}

@Builder
public record UpdateBusinessHoursDto(
@Valid
List<Summary> businessHours
){}

@Builder
public record UpdateBreakTimeDto(

@NotNull(message = "브레이크타임 시작 시간은 필수입니다.")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
LocalTime breakStartTime,

@NotNull(message = "브레이크타임 종료 시간은 필수입니다.")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
LocalTime breakEndTime
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.List;

public class BusinessHoursResDto {

Expand All @@ -20,4 +21,23 @@ public record Summary(

boolean isClosed // true = 휴무, false = 영업
){}

// 영업시간 수정 응답
@Builder
public record UpdateBusinessHoursDto(
Long storeId,
List<Summary> updatedBusinessHours
){}

// 브레이크타임 설정 응답
@Builder
public record UpdateBreakTimeDto(
Long storeId,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
LocalTime breakStartTime,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
LocalTime breakEndTime
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,23 @@ public class BusinessHours extends BaseEntity {

// 휴일 여부 (특정 요일 고정 휴무)
@Builder.Default
@Column(name = "is_holiday", nullable = false)
private boolean isHoliday = false;
@Column(name = "is_closed", nullable = false)
private boolean isClosed = false;

public void assignStore(Store store){
this.store = store;
}

// 영업시간 변경
public void update(LocalTime open, LocalTime close, boolean isClosed){
this.openTime = open;
this.closeTime = close;
this.isClosed = isClosed;
}

// 브레이크타임 변경
public void updateBreakTime(LocalTime breakStart, LocalTime breakEnd){
this.breakStartTime = breakStart;
this.breakEndTime = breakEnd;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.eatsfine.eatsfine.domain.businesshours.service;

import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursReqDto;
import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursResDto;

public interface BusinessHoursCommandService {
BusinessHoursResDto.UpdateBusinessHoursDto updateBusinessHours(
Long storeId,
BusinessHoursReqDto.UpdateBusinessHoursDto updateBusinessHoursDto
);

BusinessHoursResDto.UpdateBreakTimeDto updateBreakTime(
Long storeId,
BusinessHoursReqDto.UpdateBreakTimeDto dto
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.eatsfine.eatsfine.domain.businesshours.service;

import com.eatsfine.eatsfine.domain.businesshours.converter.BusinessHoursConverter;
import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursReqDto;
import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursResDto;
import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours;
import com.eatsfine.eatsfine.domain.businesshours.validator.BreakTimeValidator;
import com.eatsfine.eatsfine.domain.businesshours.validator.BusinessHoursValidator;
import com.eatsfine.eatsfine.domain.store.entity.Store;
import com.eatsfine.eatsfine.domain.store.exception.StoreException;
import com.eatsfine.eatsfine.domain.store.repository.StoreRepository;
import com.eatsfine.eatsfine.domain.store.status.StoreErrorStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class BusinessHoursCommandServiceImpl implements BusinessHoursCommandService {

private final StoreRepository storeRepository;

@Override
public BusinessHoursResDto.UpdateBusinessHoursDto updateBusinessHours(
Long storeId,
BusinessHoursReqDto.UpdateBusinessHoursDto dto
) {
// 영업시간 검증
BusinessHoursValidator.validateForUpdate(dto.businessHours());

Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new StoreException(StoreErrorStatus._STORE_NOT_FOUND));

dto.businessHours().forEach(s -> {
store.updateBusinessHours(
s.day(),
s.openTime(),
s.closeTime(),
s.isClosed()
);
});

return BusinessHoursConverter.toUpdateBusinessHoursDto(storeId, store.getBusinessHours());
}

@Override
public BusinessHoursResDto.UpdateBreakTimeDto updateBreakTime(
Long storeId,
BusinessHoursReqDto.UpdateBreakTimeDto dto
) {
Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new StoreException(StoreErrorStatus._STORE_NOT_FOUND));

for(BusinessHours bh : store.getBusinessHours()) {
if(bh.isClosed()) continue;
BreakTimeValidator.validateBreakTime(bh.getOpenTime(), bh.getCloseTime(), dto.breakStartTime(), dto.breakEndTime());
}

store.getBusinessHours().forEach(s -> {
if(!s.isClosed()) {
s.updateBreakTime(dto.breakStartTime(), dto.breakEndTime());
}
});

return BusinessHoursConverter.toUpdateBreakTimeDto(storeId, dto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
@RequiredArgsConstructor
public enum BusinessHoursErrorStatus implements BaseErrorCode {

_DUPLICATE_DAY_OF_WEEK(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS_400_1", "요일이 중복되었습니다."),
_BUSINESS_HOURS_NOT_COMPLETE(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS_400_2", "영업일은 7일 모두 입력되어야 합니다."),
_INVALID_BUSINESS_TIME(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS_400_3", "영업 시작 시간은 마감 시간보다 빨라야 합니다."),
_INVALID_OPEN_DAY(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS_400_4", "영업일에는 영업시간 및 마감 시간이 존재해야 합니다."),
_INVALID_CLOSED_DAY(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS_400_5", "휴무일에는 영업시간이 존재할 수 없습니다."),
_DUPLICATE_DAY_OF_WEEK(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4001", "요일이 중복되었습니다."),
_BUSINESS_HOURS_NOT_COMPLETE(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4002", "영업일은 7일 모두 입력되어야 합니다."),
_INVALID_BUSINESS_TIME(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4003", "영업 시작 시간은 마감 시간보다 빨라야 합니다."),
_INVALID_OPEN_DAY(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4004", "영업일에는 영업시간 및 마감 시간이 존재해야 합니다."),
_INVALID_CLOSED_DAY(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4005", "휴무일에는 영업시간이 존재할 수 없습니다."),
_BUSINESS_HOURS_DAY_NOT_FOUND(HttpStatus.NOT_FOUND, "BUSINESS_HOURS404", "해당 요일이 존재하지 않습니다."),
_INVALID_BREAK_TIME(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4006", "브레이크타임 시작 시간은 종료 시간보다 빨라야 합니다."),
_BREAK_TIME_OUT_OF_BUSINESS_HOURS(HttpStatus.BAD_REQUEST, "BUSINESS_HOURS4007", "브레이크타임은 영업시간 내에만 설정할 수 있습니다."),
;



private final HttpStatus httpStatus;
private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.eatsfine.eatsfine.domain.businesshours.status;

import com.eatsfine.eatsfine.global.apiPayload.code.BaseCode;
import com.eatsfine.eatsfine.global.apiPayload.code.ReasonDto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum BusinessHoursSuccessStatus implements BaseCode {

_UPDATE_BUSINESS_HOURS_SUCCESS(HttpStatus.OK, "BUSINESS_HOURS200", "영업시간이 성공적으로 수정되었습니다."),
_UPDATE_BREAKTIME_SUCCESS(HttpStatus.OK, "BUSINESS_HOURS2001", "브레이크타임이 성공적으로 설정되었습니다.")
;

private final HttpStatus httpStatus;
private final String code;
private final String message;


@Override
public ReasonDto getReason() {
return ReasonDto.builder()
.isSuccess(false)
.code(code)
.message(message)
.build();
}

@Override
public ReasonDto getReasonHttpStatus() {
return ReasonDto.builder()
.httpStatus(httpStatus)
.isSuccess(false)
.code(code)
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.eatsfine.eatsfine.domain.businesshours.validator;

import com.eatsfine.eatsfine.domain.businesshours.exception.BusinessHoursException;
import com.eatsfine.eatsfine.domain.businesshours.status.BusinessHoursErrorStatus;

import java.time.LocalTime;

public class BreakTimeValidator {

public static void validateBreakTime(LocalTime openTime, LocalTime closeTime, LocalTime breakStartTime, LocalTime breakEndTime) {

// 휴무일은 검증 대상이 아님
if(openTime == null || closeTime == null) {
return;
}

// start < end
if(!breakEndTime.isAfter(breakStartTime)) {
throw new BusinessHoursException(BusinessHoursErrorStatus._INVALID_BREAK_TIME);
}

// 브레이크타임이 영업시간 내에 존재
if(breakStartTime.isBefore(openTime) || breakEndTime.isAfter(closeTime)) {
throw new BusinessHoursException(BusinessHoursErrorStatus._BREAK_TIME_OUT_OF_BUSINESS_HOURS);
}


}
}
Loading