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
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
)
88 changes: 86 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,81 @@ 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 = DEFAULT_RESERVATION_START_HOUR,
endHour: Int = DEFAULT_RESERVATION_END_HOUR
) {
require(!endDate.isBefore(startDate)) { "endDate must not be before startDate." }
require(
startHour in DEFAULT_RESERVATION_START_HOUR..(DEFAULT_RESERVATION_END_HOUR - 1) &&
endHour in (DEFAULT_RESERVATION_START_HOUR + 1)..DEFAULT_RESERVATION_END_HOUR &&
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 {
const val DEFAULT_RESERVATION_START_HOUR = 9
const val DEFAULT_RESERVATION_END_HOUR = 16

fun createWithTimeSlots(
name: String,
description: String,
products: Collection<Product>,
tagNames: Collection<String>,
slotStartDate: LocalDate,
slotEndDate: LocalDate,
slotStartHour: Int = DEFAULT_RESERVATION_START_HOUR,
slotEndHour: Int = DEFAULT_RESERVATION_END_HOUR
): 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,41 @@
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
).request()

return reservationRepository.save(reservation)
}

fun cancelReservation(reservationId: Long): Reservation {
val reservation = reservationRepository.findById(reservationId)
?: throw IllegalArgumentException("Reservation not found: $reservationId")
val updated = reservation.cancel()
return reservationRepository.save(updated)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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 // 패키지 총액 (반정규화)
)
Loading