feat(agent-assignment): 에이전트 크리에이터 소속 관리 기능을 추가한다

This commit is contained in:
2026-04-10 02:23:10 +09:00
parent 308b79fded
commit b84f70a6bf
8 changed files with 545 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.admin.partner.agent.assignment
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.partner.agent.assignment.AssignAgentCreatorRequest
import kr.co.vividnext.sodalive.partner.agent.assignment.RemoveAgentCreatorRequest
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping("/admin/partner/agent/assignment")
class AdminAgentCreatorController(private val service: AdminAgentCreatorService) {
@PostMapping
fun assignCreator(@RequestBody request: AssignAgentCreatorRequest) = ApiResponse.ok(service.assignCreator(request))
@PostMapping("/remove")
fun removeCreator(@RequestBody request: RemoveAgentCreatorRequest) = ApiResponse.ok(service.removeCreator(request))
}

View File

@@ -0,0 +1,87 @@
package kr.co.vividnext.sodalive.admin.partner.agent.assignment
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelation
import kr.co.vividnext.sodalive.partner.agent.assignment.AgentCreatorRelationRepository
import kr.co.vividnext.sodalive.partner.agent.assignment.AssignAgentCreatorRequest
import kr.co.vividnext.sodalive.partner.agent.assignment.RemoveAgentCreatorRequest
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
@Service
class AdminAgentCreatorService(
private val relationRepository: AgentCreatorRelationRepository,
private val memberRepository: MemberRepository
) {
@Transactional
fun assignCreator(request: AssignAgentCreatorRequest) {
if (request.agentId == request.creatorId) {
throw SodaException(messageKey = "partner.agent.assignment.invalid_relation")
}
val agent = memberRepository.findByIdOrNull(request.agentId)
?: throw SodaException(messageKey = "partner.agent.assignment.agent_not_found")
if (agent.role != MemberRole.AGENT) {
throw SodaException(messageKey = "partner.agent.assignment.invalid_agent")
}
val creator = memberRepository.findByIdForUpdate(request.creatorId)
?: throw SodaException(messageKey = "partner.agent.assignment.creator_not_found")
if (creator.role != MemberRole.CREATOR) {
throw SodaException(messageKey = "partner.agent.assignment.invalid_creator")
}
val existingRelations = relationRepository.findAllByCreatorIdOrderByAssignedAtAsc(request.creatorId)
if (hasAssignmentOverlap(existingRelations, request.assignedAt)) {
throw SodaException(messageKey = "partner.agent.assignment.assignment_overlap")
}
val relation = AgentCreatorRelation()
relation.agent = agent
relation.creator = creator
relation.assignedAt = request.assignedAt
try {
relationRepository.saveAndFlush(relation)
} catch (e: DataIntegrityViolationException) {
relationRepository.findFirstByCreatorIdAndUnassignedAtIsNull(request.creatorId)
?: throw e
throw SodaException(messageKey = "partner.agent.assignment.assignment_overlap")
}
}
@Transactional
fun removeCreator(request: RemoveAgentCreatorRequest) {
val creator = memberRepository.findByIdForUpdate(request.creatorId)
?: throw SodaException(messageKey = "partner.agent.assignment.creator_not_found")
if (creator.role != MemberRole.CREATOR) {
throw SodaException(messageKey = "partner.agent.assignment.invalid_creator")
}
val relation = relationRepository.findFirstByCreatorIdAndUnassignedAtIsNull(request.creatorId)
?: throw SodaException(messageKey = "partner.agent.assignment.not_found")
val assignedAt = relation.assignedAt
?: throw SodaException(messageKey = "partner.agent.assignment.not_found")
if (!request.unassignedAt.isAfter(assignedAt)) {
throw SodaException(messageKey = "partner.agent.assignment.invalid_unassigned_at")
}
relation.unassignedAt = request.unassignedAt
relationRepository.save(relation)
}
private fun hasAssignmentOverlap(
existingRelations: List<AgentCreatorRelation>,
assignedAt: LocalDateTime
): Boolean {
return existingRelations.any { relation ->
val existingUnassignedAt = relation.unassignedAt
existingUnassignedAt == null || assignedAt.isBefore(existingUnassignedAt)
}
}
}

View File

@@ -0,0 +1,27 @@
package kr.co.vividnext.sodalive.partner.agent.assignment
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.member.Member
import java.time.LocalDateTime
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
class AgentCreatorRelation : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "agent_id", nullable = false)
var agent: Member? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_id", nullable = false)
var creator: Member? = null
@Column(nullable = false)
var assignedAt: LocalDateTime? = null
@Column(nullable = true)
var unassignedAt: LocalDateTime? = null
}

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.partner.agent.assignment
import org.springframework.data.jpa.repository.JpaRepository
interface AgentCreatorRelationRepository : JpaRepository<AgentCreatorRelation, Long> {
fun findAllByCreatorIdOrderByAssignedAtAsc(creatorId: Long): List<AgentCreatorRelation>
fun findFirstByCreatorIdAndUnassignedAtIsNull(creatorId: Long): AgentCreatorRelation?
}

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.partner.agent.assignment
import java.time.LocalDateTime
data class AssignAgentCreatorRequest(
val agentId: Long,
val creatorId: Long,
val assignedAt: LocalDateTime
)

View File

@@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.partner.agent.assignment
import java.time.LocalDateTime
data class RemoveAgentCreatorRequest(
val creatorId: Long,
val unassignedAt: LocalDateTime
)