diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/controller/BusinessHoursController.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/controller/BusinessHoursController.java new file mode 100644 index 0000000..3c0aa8f --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/controller/BusinessHoursController.java @@ -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 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 updateBreakTime( + @PathVariable Long storeId, + @RequestBody BusinessHoursReqDto.UpdateBreakTimeDto dto + ){ + return ApiResponse.of( + BusinessHoursSuccessStatus._UPDATE_BREAKTIME_SUCCESS, + businessHoursCommandService.updateBreakTime(storeId, dto) + ); + } + +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/converter/BusinessHoursConverter.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/converter/BusinessHoursConverter.java index ff7d01b..e74b337 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/converter/BusinessHoursConverter.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/converter/BusinessHoursConverter.java @@ -4,14 +4,16 @@ 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(); } @@ -19,9 +21,11 @@ public static BusinessHours toEntity(BusinessHoursReqDto.Summary dto) { 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(); } @@ -33,4 +37,23 @@ public static BusinessHoursResDto.Summary toSummary(BusinessHours bh) { .isClosed(false) .build(); } + + public static BusinessHoursResDto.UpdateBusinessHoursDto toUpdateBusinessHoursDto(Long storeId, List 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(); + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursReqDto.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursReqDto.java index be14695..10db26c 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursReqDto.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursReqDto.java @@ -1,11 +1,13 @@ 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 { @@ -13,7 +15,7 @@ public class BusinessHoursReqDto { public record Summary( @NotNull(message = "요일은 필수입니다.") - DayOfWeek dayOfWeek, + DayOfWeek day, @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm") LocalTime openTime, @@ -23,4 +25,22 @@ public record Summary( boolean isClosed ){} + + @Builder + public record UpdateBusinessHoursDto( + @Valid + List 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 + ){} } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursResDto.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursResDto.java index bae2100..d767486 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursResDto.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/dto/BusinessHoursResDto.java @@ -5,6 +5,7 @@ import java.time.DayOfWeek; import java.time.LocalTime; +import java.util.List; public class BusinessHoursResDto { @@ -20,4 +21,23 @@ public record Summary( boolean isClosed // true = 휴무, false = 영업 ){} + + // 영업시간 수정 응답 + @Builder + public record UpdateBusinessHoursDto( + Long storeId, + List 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 + ){} } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/entity/BusinessHours.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/entity/BusinessHours.java index 61883ae..b9a332f 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/entity/BusinessHours.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/entity/BusinessHours.java @@ -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; + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandService.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandService.java new file mode 100644 index 0000000..e752624 --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandService.java @@ -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 + ); + +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandServiceImpl.java new file mode 100644 index 0000000..d5416dd --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/service/BusinessHoursCommandServiceImpl.java @@ -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); + } +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursErrorStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursErrorStatus.java index e9a9310..59e1331 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursErrorStatus.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursErrorStatus.java @@ -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; diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursSuccessStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursSuccessStatus.java new file mode 100644 index 0000000..b0a12e4 --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/status/BusinessHoursSuccessStatus.java @@ -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(); + } +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BreakTimeValidator.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BreakTimeValidator.java new file mode 100644 index 0000000..a8766dd --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BreakTimeValidator.java @@ -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); + } + + + } +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BusinessHoursValidator.java b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BusinessHoursValidator.java index f173851..6313b7e 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BusinessHoursValidator.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/businesshours/validator/BusinessHoursValidator.java @@ -10,15 +10,20 @@ import java.util.Set; public class BusinessHoursValidator { - public static void validate(List dto) { + public static void validateForCreate(List dto) { validateComplete(dto); validateDuplicateDayOfWeek(dto); validateOpenDay(dto); validateClosedDay(dto); validateOpenCloseTime(dto); + } - + public static void validateForUpdate(List dto) { + validateDuplicateDayOfWeek(dto); + validateOpenDay(dto); + validateClosedDay(dto); + validateOpenCloseTime(dto); } // 7일 모두 입력 여부 검증 @@ -43,7 +48,7 @@ private static void validateOpenCloseTime(List dto) private static void validateDuplicateDayOfWeek(List dto) { Set set = new HashSet<>(); for(BusinessHoursReqDto.Summary s: dto) { - if(!set.add(s.dayOfWeek())) { + if(!set.add(s.day())) { throw new BusinessHoursException(BusinessHoursErrorStatus._DUPLICATE_DAY_OF_WEEK); } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/controller/StoreController.java b/src/main/java/com/eatsfine/eatsfine/domain/store/controller/StoreController.java index c207c4b..6527f47 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/controller/StoreController.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/controller/StoreController.java @@ -57,4 +57,18 @@ public ApiResponse getStoreDetail(@PathVariable Long return ApiResponse.of(StoreSuccessStatus._STORE_DETAIL_FOUND, storeQueryService.getStoreDetail(storeId)); } + @Operation( + summary = "가게 기본 정보 수정", + description = "가게 기본 정보(영업시간, 브레이크타임 제외)를 수정합니다. " + + "영업시간, 브레이크타임, 이미지는 별도 엔티티/컬렉션이므로 개별 API로 분리" + ) + @PatchMapping("/stores/{storeId}") + public ApiResponse updateStoreBasicInfo( + @PathVariable Long storeId, + @Valid @RequestBody StoreReqDto.StoreUpdateDto dto + ) { + return ApiResponse.of(StoreSuccessStatus._STORE_UPDATE_SUCCESS, storeCommandService.updateBasicInfo(storeId, dto)); + } + + } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java b/src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java index 759aac6..11f7ac4 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java @@ -1,11 +1,13 @@ package com.eatsfine.eatsfine.domain.store.converter; import com.eatsfine.eatsfine.domain.businesshours.converter.BusinessHoursConverter; +import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours; import com.eatsfine.eatsfine.domain.store.dto.StoreResDto; import com.eatsfine.eatsfine.domain.store.entity.Store; import java.math.BigDecimal; import java.util.Collections; +import java.util.List; public class StoreConverter { @@ -30,6 +32,11 @@ public static StoreResDto.StoreSearchDto toSearchDto(Store store, Double distanc } public static StoreResDto.StoreDetailDto toDetailDto(Store store, boolean isOpenNow) { + BusinessHours anyOpenDay = store.getBusinessHours().stream() + .filter(bh -> !bh.isClosed()) + .findFirst() + .orElse(null); + return StoreResDto.StoreDetailDto.builder() .storeId(store.getId()) .storeName(store.getStoreName()) @@ -46,8 +53,17 @@ public static StoreResDto.StoreDetailDto toDetailDto(Store store, boolean isOpen store.getBusinessHours().stream() .map(BusinessHoursConverter::toSummary) .toList()) + .breakStartTime(anyOpenDay != null ? anyOpenDay.getBreakStartTime() : null) + .breakEndTime(anyOpenDay != null ? anyOpenDay.getBreakEndTime() : null) .isOpenNow(isOpenNow) // 추후 영업 여부 판단 로직 구현 예정 .build(); } + + public static StoreResDto.StoreUpdateDto toUpdateDto(Long storeId, List updatedFields) { + return StoreResDto.StoreUpdateDto.builder() + .storeId(storeId) + .updatedFields(updatedFields) + .build(); } +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreReqDto.java b/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreReqDto.java index 3e7ff19..7330116 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreReqDto.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreReqDto.java @@ -6,6 +6,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.Builder; import java.util.List; @@ -29,6 +30,10 @@ public record StoreCreateDto( @NotBlank(message = "주소는 필수입니다.") String address, + @Pattern( + regexp = "^0\\d{1,2}-\\d{3,4}-\\d{4}$", + message = "전화번호 형식이 올바르지 않습니다." + ) @NotBlank(message = "전화번호는 필수입니다.") String phoneNumber, @@ -46,4 +51,25 @@ public record StoreCreateDto( @Valid List businessHours ){} + + @Builder + public record StoreUpdateDto( + String storeName, + + String description, + + @Pattern( + regexp = "^0\\d{1,2}-\\d{3,4}-\\d{4}$", + message = "전화번호 형식이 올바르지 않습니다. (예: 02-123-4567, 010-1234-5678)" + ) + String phoneNumber, + + Category category, + + Integer minPrice, + + DepositRate depositRate, + + Integer bookingIntervalMinutes + ){} } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreResDto.java b/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreResDto.java index 7cd66f6..a674eaa 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreResDto.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/dto/StoreResDto.java @@ -2,9 +2,11 @@ import com.eatsfine.eatsfine.domain.businesshours.dto.BusinessHoursResDto; import com.eatsfine.eatsfine.domain.store.enums.Category; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; import java.math.BigDecimal; +import java.time.LocalTime; import java.util.List; public class StoreResDto { @@ -56,6 +58,13 @@ public record StoreDetailDto( String mainImageUrl, List tableImageUrls, List businessHours, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm") + LocalTime breakStartTime, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm") + LocalTime breakEndTime, + boolean isOpenNow ){} @@ -65,4 +74,11 @@ public record uploadMainImageResDto( String mainImageUrl ) {} + // 식당 수정 응답 + @Builder + public record StoreUpdateDto( + Long storeId, + List updatedFields + ){}; + } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java b/src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java index 3717efd..9f8dc34 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java @@ -1,7 +1,10 @@ package com.eatsfine.eatsfine.domain.store.entity; import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours; +import com.eatsfine.eatsfine.domain.businesshours.exception.BusinessHoursException; +import com.eatsfine.eatsfine.domain.businesshours.status.BusinessHoursErrorStatus; import com.eatsfine.eatsfine.domain.region.entity.Region; +import com.eatsfine.eatsfine.domain.store.dto.StoreReqDto; import com.eatsfine.eatsfine.domain.store.enums.Category; import com.eatsfine.eatsfine.domain.store.enums.DepositRate; import com.eatsfine.eatsfine.domain.table_layout.entity.TableLayout; @@ -16,6 +19,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.time.DayOfWeek; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -92,8 +96,6 @@ public class Store extends BaseEntity { private List tableImages = new ArrayList<>(); // StoreTable이 아닌 TableLayout 엔티티 참조 -// @OneToMany(mappedBy = "store") -// private List storeTables = new ArrayList<>(); @Builder.Default @OneToMany(mappedBy = "store") @@ -109,6 +111,15 @@ public void removeBusinessHours(BusinessHours businessHours) { businessHours.assignStore(null); } + // 영업시간 변경 + public void updateBusinessHours(DayOfWeek dayOfWeek, LocalTime open, LocalTime close, boolean isClosed) { + BusinessHours businessHours = this.businessHours.stream() + .filter(bh -> bh.getDayOfWeek() == dayOfWeek) + .findFirst() + .orElseThrow(() -> new BusinessHoursException(BusinessHoursErrorStatus._BUSINESS_HOURS_DAY_NOT_FOUND)); + + businessHours.update(open, close, isClosed); + } public void addTableImage(TableImage tableImage) { this.tableImages.add(tableImage); tableImage.assignStore(this); @@ -143,4 +154,35 @@ public BigDecimal calculateDepositAmount() { // StoreTable에 대한 연관관계 편의 메서드는 추후 추가 예정 + // 가게 기본 정보 변경 메서드 + public void updateBasicInfo(StoreReqDto.StoreUpdateDto dto) { + if(dto.storeName() != null) { + this.storeName = dto.storeName(); + } + + if(dto.description() != null) { + this.description = dto.description(); + } + + if(dto.phoneNumber() != null) { + this.phoneNumber = dto.phoneNumber(); + } + + if(dto.category() != null) { + this.category = dto.category(); + } + + if(dto.minPrice() != null) { + this.minPrice = dto.minPrice(); + } + + if(dto.depositRate() != null) { + this.depositRate = dto.depositRate(); + } + + if(dto.bookingIntervalMinutes() != null) { + this.bookingIntervalMinutes = dto.bookingIntervalMinutes(); + } + } + } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandService.java b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandService.java index f79fe42..65ab017 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandService.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandService.java @@ -5,4 +5,5 @@ public interface StoreCommandService { StoreResDto.StoreCreateDto createStore(StoreReqDto.StoreCreateDto storeCreateDto); + StoreResDto.StoreUpdateDto updateBasicInfo(Long storeId, StoreReqDto.StoreUpdateDto storeUpdateDto); } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java index f843fb6..982f0be 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java @@ -12,9 +12,13 @@ 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 jakarta.transaction.Transactional; +import com.eatsfine.eatsfine.domain.store.status.StoreErrorStatus; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; @Service @Transactional @@ -30,7 +34,7 @@ public StoreResDto.StoreCreateDto createStore(StoreReqDto.StoreCreateDto dto) { .orElseThrow(() -> new StoreException(RegionErrorStatus._REGION_NOT_FOUND)); // 영업시간 정상 여부 검증 - BusinessHoursValidator.validate(dto.businessHours()); + BusinessHoursValidator.validateForCreate(dto.businessHours()); Store store = Store.builder() .owner(null) // User 도메인 머지 후 owner 처리 예정 @@ -57,4 +61,31 @@ public StoreResDto.StoreCreateDto createStore(StoreReqDto.StoreCreateDto dto) { return StoreConverter.toCreateDto(savedStore); } + // 가게 기본 정보 수정 (필드) + @Override + public StoreResDto.StoreUpdateDto updateBasicInfo(Long storeId, StoreReqDto.StoreUpdateDto dto) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorStatus._STORE_NOT_FOUND)); + + store.updateBasicInfo(dto); + List updatedFields = extractUpdatedFields(dto); + + return StoreConverter.toUpdateDto(storeId, updatedFields); + } + + // 수정된 필드 목록 + public List extractUpdatedFields(StoreReqDto.StoreUpdateDto dto) { + List updated = new ArrayList<>(); + + if(dto.storeName() != null) updated.add("storeName"); + if(dto.description() != null) updated.add("description"); + if(dto.phoneNumber() != null) updated.add("phoneNumber"); + if(dto.category() != null) updated.add("category"); + if(dto.minPrice() != null) updated.add("minPrice"); + if(dto.depositRate() != null) updated.add("depositRate"); + if(dto.bookingIntervalMinutes() != null) updated.add("bookingIntervalMinutes"); + + return updated; + } + } \ No newline at end of file diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java index 7db9255..2c93816 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java @@ -81,7 +81,7 @@ public boolean isOpenNow(Store store, LocalDateTime now) { return store.findBusinessHoursByDay(dayOfWeek) .map(bh -> { - if (bh.isHoliday()) return false; + if (bh.isClosed()) return false; if ((bh.getBreakStartTime() != null && bh.getBreakEndTime() != null)) { if (!time.isBefore(bh.getBreakStartTime()) && (time.isBefore(bh.getBreakEndTime()))) { diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/status/StoreSuccessStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/store/status/StoreSuccessStatus.java index 067ef69..088f093 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/store/status/StoreSuccessStatus.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/store/status/StoreSuccessStatus.java @@ -14,9 +14,11 @@ public enum StoreSuccessStatus implements BaseCode { _STORE_SEARCH_SUCCESS(HttpStatus.OK, "STORE2002", "성공적으로 가게를 검색했습니다."), - _STORE_DETAIL_FOUND(HttpStatus.FOUND, "STORE_DETAIL200", "성공적으로 가게 상세 리뷰를 조회했습니다."), + _STORE_DETAIL_FOUND(HttpStatus.OK, "STORE2003", "성공적으로 가게 상세 리뷰를 조회했습니다."), - _STORE_CREATED(HttpStatus.CREATED, "STORE201", "성공적으로 가게를 등록했습니다.") + _STORE_CREATED(HttpStatus.CREATED, "STORE201", "성공적으로 가게를 등록했습니다."), + + _STORE_UPDATE_SUCCESS(HttpStatus.OK, "STORE2004", "성공적으로 가게 기본 정보를 수정했습니다.") ;