Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package com.study.core.product.domain

interface ProductRepository {

fun findAllByIdIn(productIds: List<Long>): List<Product>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import com.study.core.product.domain.Product
import org.springframework.data.repository.CrudRepository

interface ProductJpaRepository : CrudRepository<Product, Long> {

fun findAllByIdIn(productIds: List<Long>): List<Product>
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.study.core.product.infrastructure

import com.study.core.product.domain.Product
import com.study.core.product.domain.ProductRepository
import org.springframework.stereotype.Repository

@Repository
class ProductRepositoryImpl(
private val productJpaRepository: ProductJpaRepository
) : ProductRepository {

override fun findAllByIdIn(productIds: List<Long>): List<Product> {
return productJpaRepository.findAllByIdIn(productIds)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
package com.study.core.productpackage.application

import com.study.core.product.domain.Product
import com.study.core.product.domain.ProductRepository
import com.study.core.productpackage.application.dto.CreatePackageCommand
import com.study.core.productpackage.domain.Package
import com.study.core.productpackage.infrastructure.PackageCommandRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

/**
* TODO : 도메인 설계 Merge이후 작업
*/
@Transactional
@Service
class PackageCommandService {
class PackageCommandService(
private val packageCommandRepository: PackageCommandRepository,
private val productRepository: ProductRepository,
) {

fun createPackage(): Package {
TODO()
fun createPackage(command: CreatePackageCommand): Package {
val products = findProducts(command.productIds)
val pkg = Package.createWithTimeSlots(
name = command.name,
description = command.description,
products = products,
tagNames = command.tagNames,
slotStartDate = command.slotStartDate,
slotEndDate = command.slotEndDate,
slotStartHour = command.slotStartHour,
slotEndHour = command.slotEndHour
)

return packageCommandRepository.save(pkg)
}

private fun findProducts(productIds: List<Long>): List<Product> {
val products = productRepository.findAllByIdIn(productIds)
require(products.size == productIds.size) { "One or more products are not found." }
return products
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.study.core.productpackage.application.dto

import java.time.LocalDate

data class CreatePackageCommand(
val name: String,
val description: String,
val slotStartDate: LocalDate, // 패키지 생성 시 예약 시작 가능한 날짜
val slotEndDate: LocalDate, // 패키지 생성 시 패키지 마감 날짜
val slotStartHour: Int = 9, // 예약 가능한 타임 슬롯 시작 지점
val slotEndHour: Int = 16, // 예약 가능한 타임 슬록 마지막 지점
val productIds: List<Long> = emptyList(), // 예약에 포함할 패키지 ids
val tagNames: List<String> = emptyList() // 패키지에 해당하는 tags
)
85 changes: 83 additions & 2 deletions src/main/kotlin/com/study/core/productpackage/domain/Package.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.study.core.productpackage.domain

import com.study.core.global.BaseEntity
import com.study.core.product.domain.Product
import com.study.core.reservation.domain.ReservationSlot
import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.Entity
Expand All @@ -12,6 +14,8 @@ import jakarta.persistence.OneToMany
import jakarta.persistence.Table
import jakarta.persistence.Transient
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalTime

@Entity
@Table(name = "packages")
Expand All @@ -26,11 +30,14 @@ class Package(
@Column(nullable = false, columnDefinition = "TEXT")
val description: String,

@OneToMany(mappedBy = "pkg", cascade = [CascadeType.PERSIST], orphanRemoval = true, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "pkg", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
val packageProducts: MutableList<PackageProduct> = mutableListOf(),

@OneToMany(mappedBy = "pkg", cascade = [CascadeType.PERSIST], orphanRemoval = true, fetch = FetchType.LAZY)
@OneToMany(mappedBy = "pkg", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
val packageTags: MutableList<PackageTag> = mutableListOf(),

@OneToMany(mappedBy = "pkg", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
val reservationSlots: MutableList<ReservationSlot> = mutableListOf(),
) : BaseEntity() {

@get:Transient
Expand All @@ -45,4 +52,78 @@ class Package(
get() = packageProducts.fold(BigDecimal.ZERO) { totalPrice, packageProduct ->
totalPrice + packageProduct.product.price
}

fun addProducts(products: Collection<Product>) {
products.distinctBy { it.id }
.forEach { product ->
packageProducts.add(PackageProduct(pkg = this, product = product))
}
}

fun addTags(tagNames: Collection<String>) {
tagNames.map { it.trim() }
.filter { it.isNotEmpty() }
.distinct()
.forEach { name ->
packageTags.add(PackageTag(pkg = this, tagName = name))
}
}

fun addReservationSlots(
startDate: LocalDate,
endDate: LocalDate,
startHour: Int = 9,
endHour: Int = 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시작시간, 종료시간을 상수로 선언하고, createWithTimeSlots의 slotStartHour, slotEndHour에도 같이 사용하는 건 어떤가요??

) {
require(!endDate.isBefore(startDate)) { "endDate must not be before startDate." }
require(
startHour in 9..15 &&
endHour in 10..16 &&
startHour < endHour
) { "Slot hours must be between 09 and 16." }

var currentDate = startDate
while (!currentDate.isAfter(endDate)) {
for (hour in startHour until endHour) {
val startTime = LocalTime.of(hour, 0)
val endTime = startTime.plusHours(1)
reservationSlots.add(
ReservationSlot(
pkg = this,
reservationDate = currentDate,
startTime = startTime,
endTime = endTime
)
)
}
currentDate = currentDate.plusDays(1)
}
}

companion object {
fun createWithTimeSlots(
name: String,
description: String,
products: Collection<Product>,
tagNames: Collection<String>,
slotStartDate: LocalDate,
slotEndDate: LocalDate,
slotStartHour: Int = 9,
slotEndHour: Int = 16
): Package {
val pkg = Package(
name = name,
description = description
)
pkg.addProducts(products)
pkg.addTags(tagNames)
pkg.addReservationSlots(
startDate = slotStartDate,
endDate = slotEndDate,
startHour = slotStartHour,
endHour = slotEndHour
)
return pkg
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.study.core.productpackage.domain

import com.study.core.global.BaseEntity
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
Expand All @@ -15,7 +16,7 @@ import jakarta.persistence.UniqueConstraint
@Table(
name = "package_tag",
uniqueConstraints = [
UniqueConstraint(name = "uk_package_tag", columnNames = ["package_id", "tag_id"])
UniqueConstraint(name = "uk_package_tag", columnNames = ["package_id", "tag_name"])
]
)
class PackageTag(
Expand All @@ -27,7 +28,6 @@ class PackageTag(
@JoinColumn(name = "package_id")
val pkg: Package,

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tag_id")
val tag: Tag
@Column(name = "tag_name", nullable = false, length = 50)
val tagName: String
) : BaseEntity()
20 changes: 0 additions & 20 deletions src/main/kotlin/com/study/core/productpackage/domain/Tag.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.study.core.productpackage.infrastructure

import com.study.core.productpackage.domain.Package
import org.springframework.stereotype.Repository

@Repository
class PackageCommandRepository(
private val packageJpaRepository: PackageJpaRepository
) {

fun save(pkg: Package): Package = packageJpaRepository.save(pkg)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.study.core.reservation.application

import com.study.core.reservation.application.dto.CreateReservationCommand
import com.study.core.reservation.domain.Reservation
import com.study.core.reservation.domain.ReservationRepository
import com.study.core.reservation.domain.ReservationSlotRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Transactional
@Service
class ReservationCommandService(
private val reservationRepository: ReservationRepository,
private val reservationSlotRepository: ReservationSlotRepository
) {

fun createReservation(command: CreateReservationCommand): Reservation {
val reservationTimeSlot = reservationSlotRepository.findByIdForUpdate(command.reservationSlotId)
?: throw IllegalArgumentException("Reservation slot not found: ${command.reservationSlotId}")
reservationTimeSlot.validateBookingStatusAndPackageValid(command.packageId)

val reservation = Reservation(
reservationSlot = reservationTimeSlot,
userId = command.userId,
headCount = command.headCount,
visitDate = reservationTimeSlot.reservationDate,
visitStartTime = reservationTimeSlot.startTime,
visitEndTime = reservationTimeSlot.endTime,
totalPrice = command.totalPrice,
requestMessage = command.requestMessage
).request()

return reservationRepository.save(reservation)
}

fun cancelReservation(reservationId: Long, cancelMessage: String? = null): Reservation {
val reservation = reservationRepository.findById(reservationId)
?: throw IllegalArgumentException("Reservation not found: $reservationId")
val updated = reservation.cancel(cancelMessage)
return reservationRepository.save(updated)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.study.core.reservation.application.dto

import java.math.BigDecimal

data class CreateReservationCommand(
val packageId: Long,
val reservationSlotId: Long, // 예약 가능한 타임슬롯 id
val userId: Long,
val headCount: Int, // 인원 수
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인원 수, 예약 요청 메시지는 피그마 상으로는 예약 시에 입력받고 있는 것 같지 않은데 어떤 방식으로 입력받는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 그러네요 예약 요청 메시지 같은 게 보통 있어서 관성적으로 추가한 것 같은데 제거하겠습니다.

val totalPrice: BigDecimal, // 패키지 총액 (반정규화)
val requestMessage: String? = null // 예약 요청 메시지
)
Loading