diff --git a/backend/src/main/java/de/effigenix/application/inventory/ListStorageLocations.java b/backend/src/main/java/de/effigenix/application/inventory/ListStorageLocations.java index d7a1d34..a413989 100644 --- a/backend/src/main/java/de/effigenix/application/inventory/ListStorageLocations.java +++ b/backend/src/main/java/de/effigenix/application/inventory/ListStorageLocations.java @@ -18,16 +18,8 @@ public class ListStorageLocations { if (storageType != null) { return findByStorageType(storageType, active); } - if (active == null) { - return mapResult(storageLocationRepository.findAll()); - } - var result = mapResult(storageLocationRepository.findAll()); - if (result.isSuccess()) { - return Result.success(result.unsafeGetValue().stream() - .filter(loc -> loc.active() == active) - .toList()); - } - return result; + return mapResult(storageLocationRepository.findAll()) + .map(locations -> filterByActive(locations, active)); } private Result> findByStorageType(String storageType, Boolean active) { @@ -38,13 +30,17 @@ public class ListStorageLocations { return Result.failure(new StorageLocationError.InvalidStorageType(storageType)); } - var result = mapResult(storageLocationRepository.findByStorageType(type)); - if (result.isSuccess() && active != null) { - return Result.success(result.unsafeGetValue().stream() - .filter(loc -> loc.active() == active) - .toList()); + return mapResult(storageLocationRepository.findByStorageType(type)) + .map(locations -> filterByActive(locations, active)); + } + + private List filterByActive(List locations, Boolean active) { + if (active == null) { + return locations; } - return result; + return locations.stream() + .filter(loc -> loc.active() == active) + .toList(); } private Result> mapResult( diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/InventoryCountController.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/InventoryCountController.java index 08c0329..5822df3 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/InventoryCountController.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/InventoryCountController.java @@ -13,7 +13,9 @@ import de.effigenix.domain.inventory.InventoryCountError; import de.effigenix.infrastructure.inventory.web.dto.CreateInventoryCountRequest; import de.effigenix.infrastructure.inventory.web.dto.InventoryCountResponse; import de.effigenix.infrastructure.inventory.web.dto.RecordCountItemRequest; +import de.effigenix.shared.common.Result; import de.effigenix.shared.security.ActorId; +import de.effigenix.shared.security.UserLookupPort; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -37,19 +39,22 @@ public class InventoryCountController { private final StartInventoryCount startInventoryCount; private final RecordCountItem recordCountItem; private final CompleteInventoryCount completeInventoryCount; + private final UserLookupPort userLookup; public InventoryCountController(CreateInventoryCount createInventoryCount, GetInventoryCount getInventoryCount, ListInventoryCounts listInventoryCounts, StartInventoryCount startInventoryCount, RecordCountItem recordCountItem, - CompleteInventoryCount completeInventoryCount) { + CompleteInventoryCount completeInventoryCount, + UserLookupPort userLookup) { this.createInventoryCount = createInventoryCount; this.getInventoryCount = getInventoryCount; this.listInventoryCounts = listInventoryCounts; this.startInventoryCount = startInventoryCount; this.recordCountItem = recordCountItem; this.completeInventoryCount = completeInventoryCount; + this.userLookup = userLookup; } @PostMapping @@ -64,14 +69,11 @@ public class InventoryCountController { authentication.getName() ); - var result = createInventoryCount.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.status(HttpStatus.CREATED) - .body(InventoryCountResponse.from(result.unsafeGetValue())); + return switch (createInventoryCount.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var count) -> ResponseEntity.status(HttpStatus.CREATED) + .body(InventoryCountResponse.from(count, userLookup)); + }; } @GetMapping("/{id}") @@ -80,13 +82,10 @@ public class InventoryCountController { @PathVariable String id, Authentication authentication ) { - var result = getInventoryCount.execute(id, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(InventoryCountResponse.from(result.unsafeGetValue())); + return switch (getInventoryCount.execute(id, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup)); + }; } @GetMapping @@ -95,16 +94,15 @@ public class InventoryCountController { @RequestParam(required = false) String storageLocationId, Authentication authentication ) { - var result = listInventoryCounts.execute(storageLocationId, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - List responses = result.unsafeGetValue().stream() - .map(InventoryCountResponse::from) - .toList(); - return ResponseEntity.ok(responses); + return switch (listInventoryCounts.execute(storageLocationId, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var counts) -> { + var responses = counts.stream() + .map(c -> InventoryCountResponse.from(c, userLookup)) + .toList(); + yield ResponseEntity.ok(responses); + } + }; } @PatchMapping("/{id}/start") @@ -113,13 +111,10 @@ public class InventoryCountController { @PathVariable String id, Authentication authentication ) { - var result = startInventoryCount.execute(id, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(InventoryCountResponse.from(result.unsafeGetValue())); + return switch (startInventoryCount.execute(id, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup)); + }; } @PatchMapping("/{id}/items/{itemId}") @@ -131,13 +126,10 @@ public class InventoryCountController { Authentication authentication ) { var cmd = new RecordCountItemCommand(id, itemId, request.actualQuantityAmount(), request.actualQuantityUnit()); - var result = recordCountItem.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(InventoryCountResponse.from(result.unsafeGetValue())); + return switch (recordCountItem.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup)); + }; } @PostMapping("/{id}/complete") @@ -147,13 +139,10 @@ public class InventoryCountController { Authentication authentication ) { var cmd = new CompleteInventoryCountCommand(id, authentication.getName()); - var result = completeInventoryCount.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new InventoryCountDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(InventoryCountResponse.from(result.unsafeGetValue())); + return switch (completeInventoryCount.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new InventoryCountDomainErrorException(err); + case Result.Success(var count) -> ResponseEntity.ok(InventoryCountResponse.from(count, userLookup)); + }; } // ==================== Exception Wrapper ==================== diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockController.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockController.java index e71019a..5e5f2f4 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockController.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockController.java @@ -22,6 +22,7 @@ import de.effigenix.application.inventory.command.ReserveStockCommand; import de.effigenix.application.inventory.command.UnblockStockBatchCommand; import de.effigenix.application.inventory.command.UpdateStockCommand; import de.effigenix.domain.inventory.StockError; +import de.effigenix.shared.common.Result; import de.effigenix.shared.security.ActorId; import de.effigenix.infrastructure.inventory.web.dto.AddStockBatchRequest; import de.effigenix.infrastructure.inventory.web.dto.BlockStockBatchRequest; @@ -93,44 +94,39 @@ public class StockController { @RequestParam(required = false) String storageLocationId, @RequestParam(required = false) String articleId ) { - var result = listStocks.execute(storageLocationId, articleId); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - List responses = result.unsafeGetValue().stream() - .map(StockResponse::from) - .toList(); - return ResponseEntity.ok(responses); + return switch (listStocks.execute(storageLocationId, articleId)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var stocks) -> { + var responses = stocks.stream() + .map(StockResponse::from) + .toList(); + yield ResponseEntity.ok(responses); + } + }; } // NOTE: Must be declared before /{id} to avoid Spring matching "below-minimum" as path variable @GetMapping("/below-minimum") @PreAuthorize("hasAuthority('STOCK_READ')") public ResponseEntity> listStocksBelowMinimum(Authentication authentication) { - var result = listStocksBelowMinimum.execute(ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - List responses = result.unsafeGetValue().stream() - .map(StockResponse::from) - .toList(); - return ResponseEntity.ok(responses); + return switch (listStocksBelowMinimum.execute(ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var stocks) -> { + var responses = stocks.stream() + .map(StockResponse::from) + .toList(); + yield ResponseEntity.ok(responses); + } + }; } @GetMapping("/{id}") @PreAuthorize("hasAuthority('STOCK_READ')") public ResponseEntity getStock(@PathVariable String id) { - var result = getStock.execute(id); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(StockResponse.from(result.unsafeGetValue())); + return switch (getStock.execute(id)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var stock) -> ResponseEntity.ok(StockResponse.from(stock)); + }; } @PostMapping @@ -147,15 +143,14 @@ public class StockController { request.minimumLevelAmount(), request.minimumLevelUnit(), request.minimumShelfLifeDays() ); - var result = createStock.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Stock created: {}", result.unsafeGetValue().id().value()); - return ResponseEntity.status(HttpStatus.CREATED) - .body(CreateStockResponse.from(result.unsafeGetValue())); + return switch (createStock.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var stock) -> { + logger.info("Stock created: {}", stock.id().value()); + yield ResponseEntity.status(HttpStatus.CREATED) + .body(CreateStockResponse.from(stock)); + } + }; } @PutMapping("/{id}") @@ -171,14 +166,13 @@ public class StockController { id, request.minimumLevelAmount(), request.minimumLevelUnit(), request.minimumShelfLifeDays() ); - var result = updateStock.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Stock updated: {}", id); - return ResponseEntity.ok(StockResponse.from(result.unsafeGetValue())); + return switch (updateStock.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var stock) -> { + logger.info("Stock updated: {}", id); + yield ResponseEntity.ok(StockResponse.from(stock)); + } + }; } @PostMapping("/{stockId}/batches") @@ -195,15 +189,14 @@ public class StockController { request.quantityAmount(), request.quantityUnit(), request.expiryDate() ); - var result = addStockBatch.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Batch added: {}", result.unsafeGetValue().id().value()); - return ResponseEntity.status(HttpStatus.CREATED) - .body(StockBatchResponse.from(result.unsafeGetValue())); + return switch (addStockBatch.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var batch) -> { + logger.info("Batch added: {}", batch.id().value()); + yield ResponseEntity.status(HttpStatus.CREATED) + .body(StockBatchResponse.from(batch)); + } + }; } @PostMapping("/{stockId}/batches/{batchId}/remove") @@ -220,14 +213,13 @@ public class StockController { stockId, batchId, request.quantityAmount(), request.quantityUnit() ); - var result = removeStockBatch.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Batch removal completed for batch {} of stock {}", batchId, stockId); - return ResponseEntity.ok().build(); + return switch (removeStockBatch.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var ignored) -> { + logger.info("Batch removal completed for batch {} of stock {}", batchId, stockId); + yield ResponseEntity.ok().build(); + } + }; } @PostMapping("/{stockId}/batches/{batchId}/block") @@ -241,14 +233,13 @@ public class StockController { logger.info("Blocking batch {} of stock {} by actor: {}", batchId, stockId, authentication.getName()); var cmd = new BlockStockBatchCommand(stockId, batchId, request.reason()); - var result = blockStockBatch.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Batch {} of stock {} blocked", batchId, stockId); - return ResponseEntity.ok().build(); + return switch (blockStockBatch.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var ignored) -> { + logger.info("Batch {} of stock {} blocked", batchId, stockId); + yield ResponseEntity.ok().build(); + } + }; } @PostMapping("/{stockId}/batches/{batchId}/unblock") @@ -261,14 +252,13 @@ public class StockController { logger.info("Unblocking batch {} of stock {} by actor: {}", batchId, stockId, authentication.getName()); var cmd = new UnblockStockBatchCommand(stockId, batchId); - var result = unblockStockBatch.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Batch {} of stock {} unblocked", batchId, stockId); - return ResponseEntity.ok().build(); + return switch (unblockStockBatch.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var ignored) -> { + logger.info("Batch {} of stock {} unblocked", batchId, stockId); + yield ResponseEntity.ok().build(); + } + }; } @PostMapping("/{stockId}/reservations") @@ -284,15 +274,14 @@ public class StockController { stockId, request.referenceType(), request.referenceId(), request.quantityAmount(), request.quantityUnit(), request.priority() ); - var result = reserveStock.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Reservation created: {} for stock {}", result.unsafeGetValue().id().value(), stockId); - return ResponseEntity.status(HttpStatus.CREATED) - .body(ReservationResponse.from(result.unsafeGetValue())); + return switch (reserveStock.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var reservation) -> { + logger.info("Reservation created: {} for stock {}", reservation.id().value(), stockId); + yield ResponseEntity.status(HttpStatus.CREATED) + .body(ReservationResponse.from(reservation)); + } + }; } @DeleteMapping("/{stockId}/reservations/{reservationId}") @@ -305,14 +294,13 @@ public class StockController { logger.info("Releasing reservation {} of stock {} by actor: {}", reservationId, stockId, authentication.getName()); var cmd = new ReleaseReservationCommand(stockId, reservationId); - var result = releaseReservation.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Reservation {} of stock {} released", reservationId, stockId); - return ResponseEntity.noContent().build(); + return switch (releaseReservation.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var ignored) -> { + logger.info("Reservation {} of stock {} released", reservationId, stockId); + yield ResponseEntity.noContent().build(); + } + }; } @PostMapping("/{stockId}/reservations/{reservationId}/confirm") @@ -325,14 +313,13 @@ public class StockController { logger.info("Confirming reservation {} of stock {} by actor: {}", reservationId, stockId, authentication.getName()); var cmd = new ConfirmReservationCommand(stockId, reservationId, authentication.getName()); - var result = confirmReservation.execute(cmd); - - if (result.isFailure()) { - throw new StockDomainErrorException(result.unsafeGetError()); - } - - logger.info("Reservation {} of stock {} confirmed", reservationId, stockId); - return ResponseEntity.noContent().build(); + return switch (confirmReservation.execute(cmd)) { + case Result.Failure(var err) -> throw new StockDomainErrorException(err); + case Result.Success(var ignored) -> { + logger.info("Reservation {} of stock {} confirmed", reservationId, stockId); + yield ResponseEntity.noContent().build(); + } + }; } public static class StockDomainErrorException extends RuntimeException { diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockMovementController.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockMovementController.java index d2d0eb5..b77d209 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockMovementController.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StockMovementController.java @@ -7,6 +7,7 @@ import de.effigenix.application.inventory.command.RecordStockMovementCommand; import de.effigenix.domain.inventory.StockMovementError; import de.effigenix.infrastructure.inventory.web.dto.RecordStockMovementRequest; import de.effigenix.infrastructure.inventory.web.dto.StockMovementResponse; +import de.effigenix.shared.common.Result; import de.effigenix.shared.security.ActorId; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -61,15 +62,14 @@ public class StockMovementController { request.reason(), request.referenceDocumentId(), authentication.getName() ); - var result = recordStockMovement.execute(cmd, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockMovementDomainErrorException(result.unsafeGetError()); - } - - logger.info("Stock movement recorded: {}", result.unsafeGetValue().id().value()); - return ResponseEntity.status(HttpStatus.CREATED) - .body(StockMovementResponse.from(result.unsafeGetValue())); + return switch (recordStockMovement.execute(cmd, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockMovementDomainErrorException(err); + case Result.Success(var movement) -> { + logger.info("Stock movement recorded: {}", movement.id().value()); + yield ResponseEntity.status(HttpStatus.CREATED) + .body(StockMovementResponse.from(movement)); + } + }; } @GetMapping @@ -85,31 +85,26 @@ public class StockMovementController { @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant to, Authentication authentication ) { - var result = listStockMovements.execute(stockId, articleId, movementType, - batchReference, from, to, - ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockMovementDomainErrorException(result.unsafeGetError()); - } - - List responses = result.unsafeGetValue().stream() - .map(StockMovementResponse::from) - .toList(); - return ResponseEntity.ok(responses); + return switch (listStockMovements.execute(stockId, articleId, movementType, + batchReference, from, to, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockMovementDomainErrorException(err); + case Result.Success(var movements) -> { + var responses = movements.stream() + .map(StockMovementResponse::from) + .toList(); + yield ResponseEntity.ok(responses); + } + }; } @GetMapping("/{id}") @PreAuthorize("hasAuthority('STOCK_MOVEMENT_READ')") public ResponseEntity getMovement(@PathVariable String id, Authentication authentication) { - var result = getStockMovement.execute(id, ActorId.of(authentication.getName())); - - if (result.isFailure()) { - throw new StockMovementDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(StockMovementResponse.from(result.unsafeGetValue())); + return switch (getStockMovement.execute(id, ActorId.of(authentication.getName()))) { + case Result.Failure(var err) -> throw new StockMovementDomainErrorException(err); + case Result.Success(var movement) -> ResponseEntity.ok(StockMovementResponse.from(movement)); + }; } public static class StockMovementDomainErrorException extends RuntimeException { diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StorageLocationController.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StorageLocationController.java index 54def14..c32df53 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StorageLocationController.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/controller/StorageLocationController.java @@ -12,6 +12,7 @@ import de.effigenix.domain.inventory.StorageLocationError; import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest; import de.effigenix.infrastructure.inventory.web.dto.StorageLocationResponse; import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest; +import de.effigenix.shared.common.Result; import de.effigenix.shared.security.ActorId; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -63,16 +64,15 @@ public class StorageLocationController { @RequestParam(value = "storageType", required = false) String storageType, @RequestParam(value = "active", required = false) Boolean active ) { - var result = listStorageLocations.execute(storageType, active); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - var response = result.unsafeGetValue().stream() - .map(StorageLocationResponse::from) - .toList(); - return ResponseEntity.ok(response); + return switch (listStorageLocations.execute(storageType, active)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var locations) -> { + var response = locations.stream() + .map(StorageLocationResponse::from) + .toList(); + yield ResponseEntity.ok(response); + } + }; } @GetMapping("/{id}") @@ -82,13 +82,10 @@ public class StorageLocationController { Authentication authentication ) { var actorId = extractActorId(authentication); - var result = getStorageLocation.execute(storageLocationId, actorId); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue())); + return switch (getStorageLocation.execute(storageLocationId, actorId)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var location) -> ResponseEntity.ok(StorageLocationResponse.from(location)); + }; } @PostMapping @@ -104,15 +101,14 @@ public class StorageLocationController { request.name(), request.storageType(), request.minTemperature(), request.maxTemperature() ); - var result = createStorageLocation.execute(cmd, actorId); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - logger.info("Storage location created: {}", request.name()); - return ResponseEntity.status(HttpStatus.CREATED) - .body(StorageLocationResponse.from(result.unsafeGetValue())); + return switch (createStorageLocation.execute(cmd, actorId)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var location) -> { + logger.info("Storage location created: {}", request.name()); + yield ResponseEntity.status(HttpStatus.CREATED) + .body(StorageLocationResponse.from(location)); + } + }; } @PutMapping("/{id}") @@ -129,14 +125,13 @@ public class StorageLocationController { storageLocationId, request.name(), request.minTemperature(), request.maxTemperature() ); - var result = updateStorageLocation.execute(cmd, actorId); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - logger.info("Storage location updated: {}", storageLocationId); - return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue())); + return switch (updateStorageLocation.execute(cmd, actorId)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var location) -> { + logger.info("Storage location updated: {}", storageLocationId); + yield ResponseEntity.ok(StorageLocationResponse.from(location)); + } + }; } @PatchMapping("/{id}/deactivate") @@ -148,14 +143,13 @@ public class StorageLocationController { var actorId = extractActorId(authentication); logger.info("Deactivating storage location: {} by actor: {}", storageLocationId, actorId.value()); - var result = deactivateStorageLocation.execute(storageLocationId, actorId); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - logger.info("Storage location deactivated: {}", storageLocationId); - return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue())); + return switch (deactivateStorageLocation.execute(storageLocationId, actorId)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var location) -> { + logger.info("Storage location deactivated: {}", storageLocationId); + yield ResponseEntity.ok(StorageLocationResponse.from(location)); + } + }; } @PatchMapping("/{id}/activate") @@ -167,14 +161,13 @@ public class StorageLocationController { var actorId = extractActorId(authentication); logger.info("Activating storage location: {} by actor: {}", storageLocationId, actorId.value()); - var result = activateStorageLocation.execute(storageLocationId, actorId); - - if (result.isFailure()) { - throw new StorageLocationDomainErrorException(result.unsafeGetError()); - } - - logger.info("Storage location activated: {}", storageLocationId); - return ResponseEntity.ok(StorageLocationResponse.from(result.unsafeGetValue())); + return switch (activateStorageLocation.execute(storageLocationId, actorId)) { + case Result.Failure(var err) -> throw new StorageLocationDomainErrorException(err); + case Result.Success(var location) -> { + logger.info("Storage location activated: {}", storageLocationId); + yield ResponseEntity.ok(StorageLocationResponse.from(location)); + } + }; } private ActorId extractActorId(Authentication authentication) { diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/dto/InventoryCountResponse.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/dto/InventoryCountResponse.java index 16d0c92..7157703 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/web/dto/InventoryCountResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/web/dto/InventoryCountResponse.java @@ -1,6 +1,7 @@ package de.effigenix.infrastructure.inventory.web.dto; import de.effigenix.domain.inventory.InventoryCount; +import de.effigenix.shared.security.UserLookupPort; import java.time.Instant; import java.time.LocalDate; @@ -16,13 +17,15 @@ public record InventoryCountResponse( Instant createdAt, List countItems ) { - public static InventoryCountResponse from(InventoryCount count) { + public static InventoryCountResponse from(InventoryCount count, UserLookupPort userLookup) { return new InventoryCountResponse( count.id().value(), count.storageLocationId().value(), count.countDate(), - count.initiatedBy(), - count.completedBy(), + userLookup.resolveUsername(count.initiatedBy()).orElse(count.initiatedBy()), + count.completedBy() != null + ? userLookup.resolveUsername(count.completedBy()).orElse(count.completedBy()) + : null, count.status().name(), count.createdAt(), count.countItems().stream() diff --git a/backend/src/main/java/de/effigenix/infrastructure/security/SpringUserLookupAdapter.java b/backend/src/main/java/de/effigenix/infrastructure/security/SpringUserLookupAdapter.java new file mode 100644 index 0000000..e847e92 --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/security/SpringUserLookupAdapter.java @@ -0,0 +1,33 @@ +package de.effigenix.infrastructure.security; + +import de.effigenix.domain.usermanagement.User; +import de.effigenix.domain.usermanagement.UserId; +import de.effigenix.domain.usermanagement.UserRepository; +import de.effigenix.shared.security.UserLookupPort; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +/** + * Spring implementation of UserLookupPort. + * + * Resolves user IDs to usernames via UserRepository. + */ +@Component +public class SpringUserLookupAdapter implements UserLookupPort { + + private final UserRepository userRepository; + + public SpringUserLookupAdapter(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public Optional resolveUsername(String userId) { + return userRepository.findById(UserId.of(userId)) + .fold( + err -> Optional.empty(), + opt -> opt.map(User::username) + ); + } +} diff --git a/backend/src/main/java/de/effigenix/shared/security/UserLookupPort.java b/backend/src/main/java/de/effigenix/shared/security/UserLookupPort.java new file mode 100644 index 0000000..e127d80 --- /dev/null +++ b/backend/src/main/java/de/effigenix/shared/security/UserLookupPort.java @@ -0,0 +1,22 @@ +package de.effigenix.shared.security; + +import java.util.Optional; + +/** + * User Lookup Port - Domain-facing interface for resolving user display information. + * + * Allows Bounded Contexts to resolve user IDs to usernames without depending + * on the User Management BC directly. + * + * Implementation: SpringUserLookupAdapter → UserRepository + */ +public interface UserLookupPort { + + /** + * Resolves a user ID to a username. + * + * @param userId The user's unique identifier + * @return The username, or empty if the user does not exist + */ + Optional resolveUsername(String userId); +}