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,26 @@
package com.eatsfine.eatsfine.domain.storetable.controller;

import com.eatsfine.eatsfine.domain.storetable.dto.req.StoreTableReqDto;
import com.eatsfine.eatsfine.domain.storetable.dto.res.StoreTableResDto;
import com.eatsfine.eatsfine.domain.storetable.exception.status.StoreTableSuccessStatus;
import com.eatsfine.eatsfine.domain.storetable.service.StoreTableCommandService;
import com.eatsfine.eatsfine.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@Tag(name = "StoreTable", description = "가게 테이블 관리 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class StoreTableController implements StoreTableControllerDocs {
private final StoreTableCommandService storeTableCommandService;

@PostMapping("/stores/{storeId}/tables")
public ApiResponse<StoreTableResDto.TableCreateDto> createTable(
@PathVariable Long storeId,
@RequestBody StoreTableReqDto.TableCreateDto dto
) {
return ApiResponse.of(StoreTableSuccessStatus._TABLE_CREATED, storeTableCommandService.createTable(storeId, dto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.eatsfine.eatsfine.domain.storetable.controller;

import com.eatsfine.eatsfine.domain.storetable.dto.req.StoreTableReqDto;
import com.eatsfine.eatsfine.domain.storetable.dto.res.StoreTableResDto;
import com.eatsfine.eatsfine.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;

public interface StoreTableControllerDocs {

@Operation(
summary = "테이블 생성",
description = """
배치도에 새 테이블을 추가합니다.

- 테이블 번호는 자동으로 순차 생성됩니다. (1번 테이블, 2번 테이블, ...)
- 좌표와 크기는 배치도 그리드 범위 내에 있어야 합니다.
- 다른 테이블과 겹치지 않아야 합니다.
- 최소 인원은 최대 인원보다 작거나 같아야 합니다.
- 활성화된 배치도에만 테이블을 추가할 수 있습니다.
"""
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "테이블 생성 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청 (좌표 범위 초과, 테이블 겹침 등)"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게 또는 배치도를 찾을 수 없음")
})
ApiResponse<StoreTableResDto.TableCreateDto> createTable(
@Parameter(description = "가게 ID", required = true, example = "1")
Long storeId,
@RequestBody @Valid StoreTableReqDto.TableCreateDto dto
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.eatsfine.eatsfine.domain.storetable.converter;

import com.eatsfine.eatsfine.domain.storetable.dto.res.StoreTableResDto;
import com.eatsfine.eatsfine.domain.storetable.entity.StoreTable;

public class StoreTableConverter {
// StoreTable Entity를 생성 응답 DTO로 변환
public static StoreTableResDto.TableCreateDto toTableCreateDto(StoreTable table) {
return StoreTableResDto.TableCreateDto.builder()
.tableId(table.getId())
.tableNumber(table.getTableNumber())
.gridX(table.getGridX())
.gridY(table.getGridY())
.widthSpan(table.getWidthSpan())
.heightSpan(table.getHeightSpan())
.minSeatCount(table.getMinSeatCount())
.maxSeatCount(table.getMaxSeatCount())
.seatsType(table.getSeatsType())
.rating(table.getRating())
.reviewCount(0) // 리뷰 기능 미구현으로 0 반환
.tableImageUrl(table.getTableImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.eatsfine.eatsfine.domain.storetable.dto.req;

import com.eatsfine.eatsfine.domain.storetable.enums.SeatsType;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public class StoreTableReqDto {
public record TableCreateDto(
@NotNull(message = "X 좌표는 필수입니다.")
@Min(value = 0, message = "X 좌표는 0 이상이어야 합니다.")
Integer gridX,

@NotNull(message = "Y 좌표는 필수입니다.")
@Min(value = 0, message = "Y 좌표는 0 이상이어야 합니다.")
Integer gridY,

@NotNull(message = "최소 인원은 필수입니다.")
@Min(value = 1, message = "최소 인원은 1명 이상이어야 합니다.")
@Max(value = 20, message = "최소 인원은 20명 이하여야 합니다.")
Integer minSeatCount,

@NotNull(message = "최대 인원은 필수입니다.")
@Min(value = 1, message = "최대 인원은 1명 이상이어야 합니다.")
@Max(value = 20, message = "최대 인원은 20명 이하여야 합니다.")
Integer maxSeatCount,

@NotNull(message = "테이블 유형은 필수입니다.")
SeatsType seatsType,

String tableImageUrl
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.eatsfine.eatsfine.domain.storetable.dto.res;

import com.eatsfine.eatsfine.domain.storetable.enums.SeatsType;
import lombok.Builder;

import java.math.BigDecimal;

public class StoreTableResDto {
@Builder
public record TableCreateDto(
Long tableId,
String tableNumber,
Integer gridX,
Integer gridY,
Integer widthSpan,
Integer heightSpan,
Integer minSeatCount,
Integer maxSeatCount,
SeatsType seatsType,
BigDecimal rating,
Integer reviewCount,
String tableImageUrl
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.eatsfine.eatsfine.domain.storetable.exception;

import com.eatsfine.eatsfine.global.apiPayload.code.BaseErrorCode;
import com.eatsfine.eatsfine.global.apiPayload.exception.GeneralException;

public class StoreTableException extends GeneralException {
public StoreTableException(BaseErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.eatsfine.eatsfine.domain.storetable.exception.status;

import com.eatsfine.eatsfine.global.apiPayload.code.BaseErrorCode;
import com.eatsfine.eatsfine.global.apiPayload.code.ErrorReasonDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum StoreTableErrorStatus implements BaseErrorCode {

_TABLE_NOT_FOUND(HttpStatus.NOT_FOUND, "TABLE404", "테이블을 찾을 수 없습니다."),
_TABLE_INVALID_SEAT_RANGE(HttpStatus.BAD_REQUEST, "TABLE400", "최소 인원은 최대 인원보다 작거나 같아야 합니다."),
_TABLE_POSITION_OUT_OF_BOUNDS(HttpStatus.BAD_REQUEST, "TABLE401", "테이블 위치가 배치도 그리드 범위를 벗어났습니다."),
_TABLE_POSITION_OVERLAPS(HttpStatus.BAD_REQUEST, "TABLE402", "해당 위치에 이미 다른 테이블이 존재합니다."),
;

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

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

@Override
public ErrorReasonDto getReasonHttpStatus() {
return ErrorReasonDto.builder()
.isSuccess(false)
.httpStatus(httpStatus)
.message(message)
.code(code)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.eatsfine.eatsfine.domain.storetable.exception.status;

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

@Getter
@AllArgsConstructor
public enum StoreTableSuccessStatus implements BaseCode {

_TABLE_CREATED(HttpStatus.CREATED, "TABLE201", "성공적으로 테이블을 생성했습니다."),
;

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

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

@Override
public ReasonDto getReasonHttpStatus() {
return ReasonDto.builder()
.isSuccess(true)
.httpStatus(httpStatus)
.message(message)
.code(code)
.build();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.eatsfine.eatsfine.domain.storetable.service;

import com.eatsfine.eatsfine.domain.storetable.dto.req.StoreTableReqDto;
import com.eatsfine.eatsfine.domain.storetable.dto.res.StoreTableResDto;

public interface StoreTableCommandService {
StoreTableResDto.TableCreateDto createTable(Long storeId, StoreTableReqDto.TableCreateDto dto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.eatsfine.eatsfine.domain.storetable.service;

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 com.eatsfine.eatsfine.domain.storetable.converter.StoreTableConverter;
import com.eatsfine.eatsfine.domain.storetable.dto.req.StoreTableReqDto;
import com.eatsfine.eatsfine.domain.storetable.dto.res.StoreTableResDto;
import com.eatsfine.eatsfine.domain.storetable.entity.StoreTable;
import com.eatsfine.eatsfine.domain.storetable.repository.StoreTableRepository;
import com.eatsfine.eatsfine.domain.storetable.validator.StoreTableValidator;
import com.eatsfine.eatsfine.domain.table_layout.entity.TableLayout;
import com.eatsfine.eatsfine.domain.table_layout.exception.TableLayoutException;
import com.eatsfine.eatsfine.domain.table_layout.exception.status.TableLayoutErrorStatus;
import com.eatsfine.eatsfine.domain.table_layout.repository.TableLayoutRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class StoreTableCommandServiceImpl implements StoreTableCommandService {
private final StoreRepository storeRepository;
private final TableLayoutRepository tableLayoutRepository;
private final StoreTableRepository storeTableRepository;

// 테이블 생성
@Override
public StoreTableResDto.TableCreateDto createTable(Long storeId, StoreTableReqDto.TableCreateDto dto) {
storeRepository.findById(storeId)
.orElseThrow(() -> new StoreException(StoreErrorStatus._STORE_NOT_FOUND));

TableLayout layout = tableLayoutRepository.findByStoreIdAndIsActiveTrue(storeId)
.orElseThrow(() -> new TableLayoutException(TableLayoutErrorStatus._LAYOUT_NOT_FOUND));

// 좌석 범위 검증
StoreTableValidator.validateSeatRange(dto.minSeatCount(), dto.maxSeatCount());

// 테이블이 그리드 범위 내인지 검증 (테이블 생성 시 크기는 1x1 크기로 고정)
StoreTableValidator.validateGridBounds(dto.gridX(), dto.gridY(), 1, 1, layout);

// 테이블 겹침 검증
StoreTableValidator.validateNoOverlap(dto.gridX(), dto.gridY(), 1, 1, layout.getTables());

// 테이블 번호 자동 생성
String tableNumber = generateTableNumber(layout);

// 테이블 생성
StoreTable newTable = StoreTable.builder()
.tableNumber(tableNumber)
.tableLayout(layout)
.gridX(dto.gridX())
.gridY(dto.gridY())
.widthSpan(1)
.heightSpan(1)
.minSeatCount(dto.minSeatCount())
.maxSeatCount(dto.maxSeatCount())
.seatsType(dto.seatsType())
.rating(BigDecimal.ZERO)
.tableImageUrl(dto.tableImageUrl())
.isDeleted(false)
.build();

StoreTable savedTable = storeTableRepository.save(newTable);

return StoreTableConverter.toTableCreateDto(savedTable);
}

private String generateTableNumber(TableLayout layout) {
List<StoreTable> tables = layout.getTables();

if (tables.isEmpty()) {
return "1번 테이블";
}

// 기존 테이블 번호 중 최대값 찾기
int maxNumber = tables.stream()
.map(StoreTable::getTableNumber)
.filter(number -> number.matches("\\d+번 테이블"))
.map(number -> {
String numPart = number.replace("번 테이블", "");
return Integer.parseInt(numPart);
})
.max(Integer::compareTo)
.orElse(0);

return String.format("%d번 테이블", maxNumber + 1);
}
}
Loading