generated from pricelees/issue-pr-template
feat: 일정 선택 후 예약 페이지로 넘어갈 때 해당 일정의 상태를 변경하는 API 추가
This commit is contained in:
parent
258b5f042d
commit
acfe787d5f
@ -83,6 +83,18 @@ class ScheduleService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun holdSchedule(id: Long) {
|
||||||
|
val schedule: ScheduleEntity = findOrThrow(id)
|
||||||
|
|
||||||
|
if (schedule.status == ScheduleStatus.AVAILABLE) {
|
||||||
|
schedule.hold()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ScheduleException(ScheduleErrorCode.SCHEDULE_NOT_AVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateSchedule(id: Long, request: ScheduleUpdateRequest) {
|
fun updateSchedule(id: Long, request: ScheduleUpdateRequest) {
|
||||||
log.info { "[ScheduleService.updateSchedule] 일정 수정 시작: id=$id, request=${request}" }
|
log.info { "[ScheduleService.updateSchedule] 일정 수정 시작: id=$id, request=${request}" }
|
||||||
|
|||||||
@ -38,6 +38,19 @@ interface ScheduleAPI {
|
|||||||
@RequestParam("themeId") themeId: Long
|
@RequestParam("themeId") themeId: Long
|
||||||
): ResponseEntity<CommonApiResponse<ScheduleRetrieveListResponse>>
|
): ResponseEntity<CommonApiResponse<ScheduleRetrieveListResponse>>
|
||||||
|
|
||||||
|
@LoginRequired
|
||||||
|
@Operation(summary = "일정을 Hold 상태로 변경", tags = ["로그인이 필요한 API"])
|
||||||
|
@ApiResponses(
|
||||||
|
ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "일정을 Hold 상태로 변경하여 중복 예약 방지",
|
||||||
|
useReturnTypeSchema = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fun holdSchedule(
|
||||||
|
@PathVariable("id") id: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "일정 상세 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "일정 상세 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "감사 정보를 포함하여 일정 상세 조회", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "감사 정보를 포함하여 일정 상세 조회", useReturnTypeSchema = true))
|
||||||
|
|||||||
@ -12,4 +12,5 @@ enum class ScheduleErrorCode(
|
|||||||
SCHEDULE_ALREADY_EXISTS(HttpStatus.CONFLICT, "S002", "이미 동일한 일정이 있어요."),
|
SCHEDULE_ALREADY_EXISTS(HttpStatus.CONFLICT, "S002", "이미 동일한 일정이 있어요."),
|
||||||
PAST_DATE_TIME(HttpStatus.BAD_REQUEST, "S003", "과거 날짜와 시간은 선택할 수 없어요."),
|
PAST_DATE_TIME(HttpStatus.BAD_REQUEST, "S003", "과거 날짜와 시간은 선택할 수 없어요."),
|
||||||
SCHEDULE_IN_USE(HttpStatus.CONFLICT, "S004", "예약이 진행중이거나 완료된 일정은 삭제할 수 없어요."),
|
SCHEDULE_IN_USE(HttpStatus.CONFLICT, "S004", "예약이 진행중이거나 완료된 일정은 삭제할 수 없어요."),
|
||||||
|
SCHEDULE_NOT_AVAILABLE(HttpStatus.CONFLICT, "S005", "예약이 완료되었거나 예약할 수 없는 일정이에요.")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,8 +29,12 @@ class ScheduleEntity(
|
|||||||
time?.let { this.time = it }
|
time?.let { this.time = it }
|
||||||
status?.let { this.status = it }
|
status?.let { this.status = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hold() {
|
||||||
|
this.status = ScheduleStatus.HOLD
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ScheduleStatus {
|
enum class ScheduleStatus {
|
||||||
AVAILABLE, PENDING, RESERVED, BLOCKED
|
AVAILABLE, HOLD, RESERVED, BLOCKED
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,15 @@ class ScheduleController(
|
|||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/schedules/{id}/hold")
|
||||||
|
override fun holdSchedule(
|
||||||
|
@PathVariable("id") id: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
|
scheduleService.holdSchedule(id)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse())
|
||||||
|
}
|
||||||
|
|
||||||
@PatchMapping("/schedules/{id}")
|
@PatchMapping("/schedules/{id}")
|
||||||
override fun updateSchedule(
|
override fun updateSchedule(
|
||||||
@PathVariable("id") id: Long,
|
@PathVariable("id") id: Long,
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
|||||||
import roomescape.schedule.web.ScheduleCreateRequest
|
import roomescape.schedule.web.ScheduleCreateRequest
|
||||||
import roomescape.schedule.web.ScheduleUpdateRequest
|
import roomescape.schedule.web.ScheduleUpdateRequest
|
||||||
import roomescape.util.*
|
import roomescape.util.*
|
||||||
|
import roomescape.util.ScheduleFixture.createRequest
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ class ScheduleApiTest(
|
|||||||
runTest(
|
runTest(
|
||||||
token = token,
|
token = token,
|
||||||
using = {
|
using = {
|
||||||
body(ScheduleFixture.createRequest)
|
body(createRequest)
|
||||||
},
|
},
|
||||||
on = {
|
on = {
|
||||||
get("/schedules/1")
|
get("/schedules/1")
|
||||||
@ -71,13 +72,13 @@ class ScheduleApiTest(
|
|||||||
test("일정 수정: PATCH /schedules/{id}") {
|
test("일정 수정: PATCH /schedules/{id}") {
|
||||||
val createdSchedule: ScheduleEntity =
|
val createdSchedule: ScheduleEntity =
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest
|
createRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = token,
|
token = token,
|
||||||
using = {
|
using = {
|
||||||
body(ScheduleFixture.createRequest)
|
body(createRequest)
|
||||||
},
|
},
|
||||||
on = {
|
on = {
|
||||||
patch("/schedules/${createdSchedule.id}")
|
patch("/schedules/${createdSchedule.id}")
|
||||||
@ -89,7 +90,7 @@ class ScheduleApiTest(
|
|||||||
test("일정 삭제: DELETE /schedules/{id}") {
|
test("일정 삭제: DELETE /schedules/{id}") {
|
||||||
val createdSchedule: ScheduleEntity =
|
val createdSchedule: ScheduleEntity =
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest
|
createRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
@ -114,7 +115,7 @@ class ScheduleApiTest(
|
|||||||
val time = LocalTime.now()
|
val time = LocalTime.now()
|
||||||
val createdSchedule: ScheduleEntity =
|
val createdSchedule: ScheduleEntity =
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date,
|
date = date,
|
||||||
time = time
|
time = time
|
||||||
)
|
)
|
||||||
@ -139,13 +140,13 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
val createdSchedule: ScheduleEntity =
|
val createdSchedule: ScheduleEntity =
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date,
|
date = date,
|
||||||
time = time
|
time = time
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date.plusDays(1L),
|
date = date.plusDays(1L),
|
||||||
time = time,
|
time = time,
|
||||||
themeId = createdSchedule.themeId
|
themeId = createdSchedule.themeId
|
||||||
@ -175,7 +176,7 @@ class ScheduleApiTest(
|
|||||||
val date = LocalDate.now().plusDays(1)
|
val date = LocalDate.now().plusDays(1)
|
||||||
for (i in 1..10) {
|
for (i in 1..10) {
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date,
|
date = date,
|
||||||
time = LocalTime.now().plusMinutes(i.toLong())
|
time = LocalTime.now().plusMinutes(i.toLong())
|
||||||
)
|
)
|
||||||
@ -199,14 +200,14 @@ class ScheduleApiTest(
|
|||||||
test("정상 응답") {
|
test("정상 응답") {
|
||||||
val date = LocalDate.now().plusDays(1)
|
val date = LocalDate.now().plusDays(1)
|
||||||
val createdSchedule = createDummySchedule(
|
val createdSchedule = createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date,
|
date = date,
|
||||||
time = LocalTime.now()
|
time = LocalTime.now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for (i in 1..10) {
|
for (i in 1..10) {
|
||||||
createDummySchedule(
|
createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date,
|
date = date,
|
||||||
time = LocalTime.now().plusMinutes(i.toLong()),
|
time = LocalTime.now().plusMinutes(i.toLong()),
|
||||||
themeId = createdSchedule.themeId
|
themeId = createdSchedule.themeId
|
||||||
@ -233,7 +234,7 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
context("관리자 페이지에서 특정 일정의 감사 정보를 조회한다.") {
|
context("관리자 페이지에서 특정 일정의 감사 정보를 조회한다.") {
|
||||||
test("정상 응답") {
|
test("정상 응답") {
|
||||||
val createdSchedule = createDummySchedule(ScheduleFixture.createRequest)
|
val createdSchedule = createDummySchedule(createRequest)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = loginUtil.loginAsAdmin(),
|
token = loginUtil.loginAsAdmin(),
|
||||||
@ -302,7 +303,7 @@ class ScheduleApiTest(
|
|||||||
runTest(
|
runTest(
|
||||||
token = token,
|
token = token,
|
||||||
using = {
|
using = {
|
||||||
body(ScheduleFixture.createRequest.copy(themeId = themeId))
|
body(createRequest.copy(themeId = themeId))
|
||||||
},
|
},
|
||||||
on = {
|
on = {
|
||||||
post("/schedules")
|
post("/schedules")
|
||||||
@ -316,9 +317,9 @@ class ScheduleApiTest(
|
|||||||
val createdSchedule: ScheduleEntity = scheduleRepository.findByIdOrNull(createdScheduleId)
|
val createdSchedule: ScheduleEntity = scheduleRepository.findByIdOrNull(createdScheduleId)
|
||||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||||
|
|
||||||
createdSchedule.date shouldBe ScheduleFixture.createRequest.date
|
createdSchedule.date shouldBe createRequest.date
|
||||||
createdSchedule.time.hour shouldBe ScheduleFixture.createRequest.time.hour
|
createdSchedule.time.hour shouldBe createRequest.time.hour
|
||||||
createdSchedule.time.minute shouldBe ScheduleFixture.createRequest.time.minute
|
createdSchedule.time.minute shouldBe createRequest.time.minute
|
||||||
createdSchedule.createdAt shouldNotBeNull {}
|
createdSchedule.createdAt shouldNotBeNull {}
|
||||||
createdSchedule.createdBy shouldNotBeNull {}
|
createdSchedule.createdBy shouldNotBeNull {}
|
||||||
createdSchedule.updatedAt shouldNotBeNull {}
|
createdSchedule.updatedAt shouldNotBeNull {}
|
||||||
@ -331,7 +332,7 @@ class ScheduleApiTest(
|
|||||||
val time = LocalTime.of(10, 0)
|
val time = LocalTime.of(10, 0)
|
||||||
|
|
||||||
val alreadyCreated: ScheduleEntity = createDummySchedule(
|
val alreadyCreated: ScheduleEntity = createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date, time = time
|
date = date, time = time
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -340,7 +341,7 @@ class ScheduleApiTest(
|
|||||||
token = token,
|
token = token,
|
||||||
using = {
|
using = {
|
||||||
body(
|
body(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = date, time = time, themeId = alreadyCreated.themeId
|
date = date, time = time, themeId = alreadyCreated.themeId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -360,7 +361,7 @@ class ScheduleApiTest(
|
|||||||
token = token,
|
token = token,
|
||||||
using = {
|
using = {
|
||||||
body(
|
body(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = LocalDate.now(),
|
date = LocalDate.now(),
|
||||||
time = LocalTime.now().minusMinutes(1)
|
time = LocalTime.now().minusMinutes(1)
|
||||||
)
|
)
|
||||||
@ -377,6 +378,76 @@ class ScheduleApiTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context("일정을 잠시 Hold 상태로 변경하여 중복 예약을 방지한다.") {
|
||||||
|
test("해당 일정이 ${ScheduleStatus.AVAILABLE} 상태이면 정상 응답") {
|
||||||
|
val createdSchedule: ScheduleEntity = createDummySchedule(createRequest)
|
||||||
|
|
||||||
|
runTest(
|
||||||
|
token = loginUtil.loginAsUser(),
|
||||||
|
on = {
|
||||||
|
patch("/schedules/${createdSchedule.id}/hold")
|
||||||
|
},
|
||||||
|
expect = {
|
||||||
|
statusCode(HttpStatus.OK.value())
|
||||||
|
}
|
||||||
|
).also {
|
||||||
|
val updatedSchedule = scheduleRepository.findByIdOrNull(createdSchedule.id)
|
||||||
|
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||||
|
|
||||||
|
updatedSchedule.status shouldBe ScheduleStatus.HOLD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("예약이 없으면 실패한다.") {
|
||||||
|
runTest(
|
||||||
|
token = loginUtil.loginAsUser(),
|
||||||
|
on = {
|
||||||
|
patch("/schedules/1/hold")
|
||||||
|
},
|
||||||
|
expect = {
|
||||||
|
statusCode(HttpStatus.NOT_FOUND.value())
|
||||||
|
body("code", equalTo(ScheduleErrorCode.SCHEDULE_NOT_FOUND.errorCode))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("해당 일정이 ${ScheduleStatus.AVAILABLE} 상태가 아니면 실패한다.") {
|
||||||
|
val createdSchedule: ScheduleEntity = createDummySchedule(createRequest)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 테스트를 위해 수정 API를 호출하여 상태를 HOLD 상태로 변경
|
||||||
|
* 생성 API에서는 일정 생성 시 AVAILABLE을 기본 상태로 지정하기 때문.
|
||||||
|
*/
|
||||||
|
runTest(
|
||||||
|
token = loginUtil.loginAsAdmin(),
|
||||||
|
using = {
|
||||||
|
body(
|
||||||
|
ScheduleUpdateRequest(
|
||||||
|
status = ScheduleStatus.HOLD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
on = {
|
||||||
|
patch("/schedules/${createdSchedule.id}")
|
||||||
|
},
|
||||||
|
expect = {
|
||||||
|
statusCode(HttpStatus.OK.value())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
runTest(
|
||||||
|
token = loginUtil.loginAsUser(),
|
||||||
|
on = {
|
||||||
|
patch("/schedules/${createdSchedule.id}/hold")
|
||||||
|
},
|
||||||
|
expect = {
|
||||||
|
statusCode(HttpStatus.CONFLICT.value())
|
||||||
|
body("code", equalTo(ScheduleErrorCode.SCHEDULE_NOT_AVAILABLE.errorCode))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context("일정을 수정한다.") {
|
context("일정을 수정한다.") {
|
||||||
val updateRequest = ScheduleUpdateRequest(
|
val updateRequest = ScheduleUpdateRequest(
|
||||||
time = LocalTime.now().plusHours(1),
|
time = LocalTime.now().plusHours(1),
|
||||||
@ -385,7 +456,7 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
test("정상 수정 및 감사 정보 변경 확인") {
|
test("정상 수정 및 감사 정보 변경 확인") {
|
||||||
val createdSchedule: ScheduleEntity = createDummySchedule(
|
val createdSchedule: ScheduleEntity = createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(
|
createRequest.copy(
|
||||||
date = LocalDate.now().plusDays(1),
|
date = LocalDate.now().plusDays(1),
|
||||||
time = LocalTime.now().plusMinutes(1),
|
time = LocalTime.now().plusMinutes(1),
|
||||||
)
|
)
|
||||||
@ -416,7 +487,7 @@ class ScheduleApiTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("입력값이 없으면 수정하지 않는다.") {
|
test("입력값이 없으면 수정하지 않는다.") {
|
||||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
val createdSchedule: ScheduleEntity = createDummySchedule(createRequest)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = loginUtil.loginAsAdmin(),
|
token = loginUtil.loginAsAdmin(),
|
||||||
@ -455,7 +526,7 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
test("입력된 날짜 + 시간이 현재 시간 이전이면 실패한다.") {
|
test("입력된 날짜 + 시간이 현재 시간 이전이면 실패한다.") {
|
||||||
val createdSchedule: ScheduleEntity = createDummySchedule(
|
val createdSchedule: ScheduleEntity = createDummySchedule(
|
||||||
ScheduleFixture.createRequest.copy(date = LocalDate.now(), time = LocalTime.now().plusMinutes(1))
|
createRequest.copy(date = LocalDate.now(), time = LocalTime.now().plusMinutes(1))
|
||||||
)
|
)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
@ -480,7 +551,7 @@ class ScheduleApiTest(
|
|||||||
|
|
||||||
context("일정을 삭제한다.") {
|
context("일정을 삭제한다.") {
|
||||||
test("정상 삭제") {
|
test("정상 삭제") {
|
||||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
val createdSchedule: ScheduleEntity = createDummySchedule(createRequest)
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = loginUtil.loginAsAdmin(),
|
token = loginUtil.loginAsAdmin(),
|
||||||
@ -496,7 +567,7 @@ class ScheduleApiTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("예약 중이거나 예약이 완료된 일정이면 실패한다.") {
|
test("예약 중이거나 예약이 완료된 일정이면 실패한다.") {
|
||||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
val createdSchedule: ScheduleEntity = createDummySchedule(createRequest)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 테스트를 위해 수정 API를 호출하여 상태를 예약 중 상태로 변경
|
* 테스트를 위해 수정 API를 호출하여 상태를 예약 중 상태로 변경
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user