Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MLT-0049 Item Callback #40

Merged
merged 10 commits into from
Oct 10, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package no.nb.mlt.wls.application.synqapi.synq

import io.swagger.v3.oas.annotations.media.Schema
import no.nb.mlt.wls.domain.model.HostName
import no.nb.mlt.wls.domain.ports.inbound.MoveItemPayload

@Schema(
description = "Payload for receiving Item updates in batch from SynQ storage system.",
Expand Down Expand Up @@ -35,7 +37,7 @@ import io.swagger.v3.oas.annotations.media.Schema
"warehouse" : "Sikringsmagasin_2"
}"""
)
data class SynqBatchItemUpdatePayload(
data class SynqBatchMoveItemPayload(
@Schema(
description = "ID of the transport unit in the SynQ storage system.",
example = "6942066642"
Expand Down Expand Up @@ -187,3 +189,18 @@ data class Position(
)
val zPosition: Int
)

fun Product.toPayload(location: String): MoveItemPayload {
return MoveItemPayload(
hostId = productId,
hostName = HostName.valueOf(hostName.uppercase()),
quantity = quantityOnHand,
location = location
)
}

fun SynqBatchMoveItemPayload.mapToItemPayloads(): List<MoveItemPayload> {
return loadUnit.map {
it.toPayload(location)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,57 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import no.nb.mlt.wls.domain.model.Owner
import no.nb.mlt.wls.domain.ports.inbound.MoveItem
import no.nb.mlt.wls.domain.ports.inbound.OrderStatusUpdate
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(path = [ "/synq/v1"])
@RequestMapping(path = ["/synq/v1"])
@Tag(name = "SynQ Controller", description = "API for receiving product and order updates from SynQ in Hermes WLS")
class SynqController(
private val moveItem: MoveItem,
private val orderStatusUpdate: OrderStatusUpdate
) {
@Operation(
summary = "Updates the status and location for items",
description = "Parses all the items from the SynQ load unit, and updates both status & location for them."
)
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "Item with given 'hostName' and 'hostId' was found and updated.",
content = [Content(schema = Schema())]
),
ApiResponse(
responseCode = "400",
description = "The payload for moving items was invalid and nothing got updated.",
content = [Content(schema = Schema())]
),
ApiResponse(
responseCode = "403",
description = "A valid 'Authorization' header is missing from the request.",
content = [Content(schema = Schema())]
),
ApiResponse(
responseCode = "404",
description = """An item for a specific 'hostName' and 'hostId' was not found.
Error message contains information about the missing item.""",
content = [Content(schema = Schema())]
)
)
@PutMapping("/item-update")
suspend fun updateItem(
@RequestBody synqBatchMoveItemPayload: SynqBatchMoveItemPayload
): ResponseEntity<Void> {
synqBatchMoveItemPayload.mapToItemPayloads().map { moveItem.moveItem(it) }
return ResponseEntity.ok().build()
}

@Operation(
summary = "Updates order status based on SynQ order status update",
description = """Finds a specified order and updates its status given the message we receive from SynQ.
Expand Down Expand Up @@ -57,15 +92,14 @@ class SynqController(
)
@PutMapping("/order-update/{owner}/{orderId}")
suspend fun updateOrder(
@AuthenticationPrincipal jwt: JwtAuthenticationToken,
@RequestBody orderUpdatePayload: SynqOrderStatusUpdatePayload,
@Parameter(description = "Owner of the order items")
@PathVariable owner: Owner,
@Parameter(description = "Order ID in the storage system")
@PathVariable orderId: String
): ResponseEntity<Unit> {
): ResponseEntity<Void> {
orderStatusUpdate.updateOrderStatus(orderUpdatePayload.hostName, orderId, orderUpdatePayload.getConvertedStatus())

return ResponseEntity.ok().build<Unit>()
return ResponseEntity.ok().build()
}
}
22 changes: 12 additions & 10 deletions src/main/kotlin/no/nb/mlt/wls/domain/WLSService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import no.nb.mlt.wls.domain.ports.inbound.GetOrder
import no.nb.mlt.wls.domain.ports.inbound.ItemMetadata
import no.nb.mlt.wls.domain.ports.inbound.ItemNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.MoveItem
import no.nb.mlt.wls.domain.ports.inbound.MoveItemPayload
import no.nb.mlt.wls.domain.ports.inbound.OrderNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.OrderStatusUpdate
import no.nb.mlt.wls.domain.ports.inbound.ServerException
Expand Down Expand Up @@ -58,20 +59,21 @@ class WLSService(
.awaitSingle()
}

override suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item {
if (quantity < 0.0) {
override suspend fun moveItem(moveItemPayload: MoveItemPayload): Item {
if (moveItemPayload.quantity < 0.0) {
throw ValidationException("Quantity can not be negative")
}
if (location.isBlank()) {
if (moveItemPayload.location.isBlank()) {
throw ValidationException("Location can not be blank")
}
val item = getItem(hostName, hostId) ?: throw ItemNotFoundException("Item with id '$hostId' does not exist for '$hostName'")
return itemRepository.moveItem(item.hostId, item.hostName, quantity, location)
val item =
getItem(
moveItemPayload.hostName,
moveItemPayload.hostId
) ?: throw ItemNotFoundException("Item with id '${moveItemPayload.hostId}' does not exist for '${moveItemPayload.hostName}'")
val movedItem = itemRepository.moveItem(item.hostId, item.hostName, moveItemPayload.quantity, moveItemPayload.location)
inventoryNotifier.itemChanged(movedItem)
return movedItem
}

override suspend fun createOrder(orderDTO: CreateOrderDTO): Order {
Expand Down
14 changes: 8 additions & 6 deletions src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import no.nb.mlt.wls.domain.model.Item
* In both cases we want to know where the item went, and if the count changed
*/
interface MoveItem {
suspend fun moveItem(
hostId: String,
hostName: HostName,
quantity: Double,
location: String
): Item
suspend fun moveItem(moveItemPayload: MoveItemPayload): Item
}

data class MoveItemPayload(
val hostId: String,
val hostName: HostName,
val quantity: Double,
val location: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ class InventoryNotifierAdapter(
private val webClient: WebClient
) : InventoryNotifier {
override fun itemChanged(item: Item) {
TODO("Future task")
if (item.callbackUrl != null) {
webClient
.post()
.uri(item.callbackUrl)
.bodyValue(item)
.retrieve()
.bodyToMono(Void::class.java)
.retry(5)
.subscribe()
}
}

override fun orderChanged(order: Order) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,25 @@ class ItemRepositoryMongoAdapter(
quantity: Double,
location: String
): Item {
mongoRepo
.findAndUpdateItemByHostNameAndHostId(hostId, hostName, quantity, location)
.timeout(Duration.ofSeconds(8))
.doOnError {
logger.error(it) {
if (it is TimeoutException) {
"Timed out while updating Item. Order ID: $hostId, Host: $hostName"
} else {
"Error while updating order"
val itemsModified =
mongoRepo
.findAndUpdateItemByHostNameAndHostId(hostName, hostId, quantity, location)
.timeout(Duration.ofSeconds(8))
.doOnError {
logger.error(it) {
if (it is TimeoutException) {
"Timed out while updating Item. Host ID: $hostId, Host: $hostName"
} else {
"Error while updating item"
}
}
}
}
.onErrorMap { ItemMovingException(it.message ?: "Item could not be moved", it) }
.awaitSingleOrNull() ?: ItemNotFoundException("Item with host ID $hostId for $hostName does not exist in WLS database")
.onErrorMap { ItemMovingException(it.message ?: "Item could not be moved", it) }
.awaitSingle()

if (itemsModified == 0L) {
throw ItemNotFoundException("Item was not found. Host ID: $hostId, Host: $hostName")
}

return getItem(hostName, hostId)!!
}
Expand All @@ -94,12 +99,12 @@ interface ItemMongoRepository : ReactiveMongoRepository<MongoItem, String> {
@Query(count = true, value = "{ '\$or': ?0 }")
fun countItemsMatchingIds(ids: List<ItemId>): Mono<Long>

@Query("{hostName: ?0, hostOrderId: ?1}")
@Query("{hostName: ?0,hostId: ?1}")
@Update("{'\$set':{quantity: ?2,location: ?3}}")
fun findAndUpdateItemByHostNameAndHostId(
hostOrderId: String,
hostName: HostName,
hostId: String,
quantity: Double,
location: String
): Mono<Void>
): Mono<Long>
}
19 changes: 15 additions & 4 deletions src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.mockk.clearAllMocks
import io.mockk.coEvery
import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import no.nb.mlt.wls.domain.model.Environment
Expand All @@ -15,6 +16,7 @@ import no.nb.mlt.wls.domain.model.Packaging
import no.nb.mlt.wls.domain.ports.inbound.CreateOrderDTO
import no.nb.mlt.wls.domain.ports.inbound.ItemMetadata
import no.nb.mlt.wls.domain.ports.inbound.ItemNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.MoveItemPayload
import no.nb.mlt.wls.domain.ports.inbound.OrderNotFoundException
import no.nb.mlt.wls.domain.ports.inbound.ValidationException
import no.nb.mlt.wls.domain.ports.outbound.InventoryNotifier
Expand Down Expand Up @@ -133,10 +135,11 @@ class WLSServiceTest {
)
coEvery { itemRepoMock.getItem(any(), any()) } returns testItem
coEvery { itemRepoMock.moveItem(any(), any(), any(), any()) } returns expectedItem
every { inventoryNotifierMock.itemChanged(any()) } answers {}

val cut = WLSService(itemRepoMock, orderRepoMock, storageSystemRepoMock, inventoryNotifierMock)
runTest {
val movedItem = cut.moveItem(testItem.hostId, testItem.hostName, 1.0, "Somewhere nice")
val movedItem = cut.moveItem(testMoveItemPayload)
assertThat(movedItem).isEqualTo(expectedItem)

coVerify(exactly = 1) { itemRepoMock.getItem(any(), any()) }
Expand All @@ -152,7 +155,7 @@ class WLSServiceTest {
val cut = WLSService(itemRepoMock, orderRepoMock, storageSystemRepoMock, inventoryNotifierMock)
runTest {
assertThrows<RuntimeException> {
cut.moveItem(testItem.hostId, testItem.hostName, 1.0, "Somewhere nice")
cut.moveItem(testMoveItemPayload)
}

coVerify(exactly = 1) { itemRepoMock.getItem(any(), any()) }
Expand All @@ -168,7 +171,7 @@ class WLSServiceTest {
val cut = WLSService(itemRepoMock, orderRepoMock, storageSystemRepoMock, inventoryNotifierMock)
runTest {
assertThrows<RuntimeException> {
cut.moveItem(testItem.hostId, testItem.hostName, -1.0, "Somewhere nice")
cut.moveItem(testMoveItemPayload.copy(quantity = -1.0))
}

coVerify(exactly = 0) { itemRepoMock.getItem(any(), any()) }
Expand All @@ -184,7 +187,7 @@ class WLSServiceTest {
val cut = WLSService(itemRepoMock, orderRepoMock, storageSystemRepoMock, inventoryNotifierMock)
runTest {
assertThrows<RuntimeException> {
cut.moveItem(testItem.hostId, testItem.hostName, 1.0, " ")
cut.moveItem(testMoveItemPayload.copy(location = " "))
}

coVerify(exactly = 0) { itemRepoMock.getItem(any(), any()) }
Expand Down Expand Up @@ -433,6 +436,14 @@ class WLSServiceTest {
)
)

private val testMoveItemPayload =
MoveItemPayload(
"12345",
HostName.AXIELL,
1.0,
"Somewhere nice"
)

private fun Order.toCreateOrderDTO() =
CreateOrderDTO(
hostName = testOrder.hostName,
Expand Down