mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 06:29:35 +01:00
refactor(inventory): unsafeGet durch switch Pattern-Matching ersetzen, UserLookupPort einführen
unsafeGet-Aufrufe in allen 4 Inventory-Controllern und ListStorageLocations durch typsicheres switch Pattern-Matching auf Result<E,T> ersetzt. Neuer SharedKernel-Port UserLookupPort ermöglicht cross-BC Auflösung von User-IDs zu Usernames (z.B. für initiatedBy/completedBy in InventoryCountResponse).
This commit is contained in:
parent
e4f4537581
commit
ae95a0284f
8 changed files with 264 additions and 246 deletions
|
|
@ -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<StorageLocationError, List<StorageLocation>> 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<StorageLocation> filterByActive(List<StorageLocation> locations, Boolean active) {
|
||||
if (active == null) {
|
||||
return locations;
|
||||
}
|
||||
return result;
|
||||
return locations.stream()
|
||||
.filter(loc -> loc.active() == active)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Result<StorageLocationError, List<StorageLocation>> mapResult(
|
||||
|
|
|
|||
|
|
@ -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<InventoryCountResponse> 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 ====================
|
||||
|
|
|
|||
|
|
@ -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<StockResponse> 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<List<StockResponse>> listStocksBelowMinimum(Authentication authentication) {
|
||||
var result = listStocksBelowMinimum.execute(ActorId.of(authentication.getName()));
|
||||
|
||||
if (result.isFailure()) {
|
||||
throw new StockDomainErrorException(result.unsafeGetError());
|
||||
}
|
||||
|
||||
List<StockResponse> 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<StockResponse> 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 {
|
||||
|
|
|
|||
|
|
@ -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<StockMovementResponse> 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<StockMovementResponse> 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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<CountItemResponse> 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()
|
||||
|
|
|
|||
|
|
@ -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<String> resolveUsername(String userId) {
|
||||
return userRepository.findById(UserId.of(userId))
|
||||
.fold(
|
||||
err -> Optional.empty(),
|
||||
opt -> opt.map(User::username)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> resolveUsername(String userId);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue