mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 19:30:16 +01:00
feat(inventory): StorageLocation bearbeiten und (de-)aktivieren (#2)
Use Cases: UpdateStorageLocation, DeactivateStorageLocation, ActivateStorageLocation
Endpoints: PUT /{id}, PATCH /{id}/deactivate, PATCH /{id}/activate
Repository: existsByNameAndIdNot für Uniqueness-Check bei Update
Tests: 14 neue Integrationstests (25 gesamt)
This commit is contained in:
parent
05878b1ce9
commit
24a6869faf
11 changed files with 493 additions and 2 deletions
|
|
@ -0,0 +1,47 @@
|
|||
package de.effigenix.application.inventory;
|
||||
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class ActivateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
|
||||
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||
// 1. Laden
|
||||
var locationId = StorageLocationId.of(storageLocationId);
|
||||
StorageLocation location;
|
||||
switch (storageLocationRepository.findById(locationId)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var opt) -> {
|
||||
if (opt.isEmpty()) {
|
||||
return Result.failure(new StorageLocationError.StorageLocationNotFound(storageLocationId));
|
||||
}
|
||||
location = opt.get();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Aktivieren
|
||||
switch (location.activate()) {
|
||||
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
// 3. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package de.effigenix.application.inventory;
|
||||
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class DeactivateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
|
||||
public DeactivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||
// 1. Laden
|
||||
var locationId = StorageLocationId.of(storageLocationId);
|
||||
StorageLocation location;
|
||||
switch (storageLocationRepository.findById(locationId)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var opt) -> {
|
||||
if (opt.isEmpty()) {
|
||||
return Result.failure(new StorageLocationError.StorageLocationNotFound(storageLocationId));
|
||||
}
|
||||
location = opt.get();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Stock-Existenz prüfen, wenn Stock BC implementiert ist
|
||||
// Akzeptanzkriterium: Deaktivierung schlägt fehl, wenn Stock am Lagerort existiert
|
||||
|
||||
// 2. Deaktivieren
|
||||
switch (location.deactivate()) {
|
||||
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
// 3. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package de.effigenix.application.inventory;
|
||||
|
||||
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class UpdateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
|
||||
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(UpdateStorageLocationCommand cmd, ActorId performedBy) {
|
||||
// 1. Laden
|
||||
var locationId = StorageLocationId.of(cmd.storageLocationId());
|
||||
StorageLocation location;
|
||||
switch (storageLocationRepository.findById(locationId)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var opt) -> {
|
||||
if (opt.isEmpty()) {
|
||||
return Result.failure(new StorageLocationError.StorageLocationNotFound(cmd.storageLocationId()));
|
||||
}
|
||||
location = opt.get();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Draft bauen + Aggregate validieren lassen
|
||||
var draft = new StorageLocationUpdateDraft(cmd.name(), cmd.minTemperature(), cmd.maxTemperature());
|
||||
switch (location.update(draft)) {
|
||||
case Result.Failure(var err) -> { return Result.failure(err); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
// 3. Uniqueness-Check (nur wenn Name geändert wurde)
|
||||
if (cmd.name() != null) {
|
||||
switch (storageLocationRepository.existsByNameAndIdNot(location.name(), locationId)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var exists) -> {
|
||||
if (exists) {
|
||||
return Result.failure(new StorageLocationError.NameAlreadyExists(cmd.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package de.effigenix.application.inventory.command;
|
||||
|
||||
public record UpdateStorageLocationCommand(
|
||||
String storageLocationId,
|
||||
String name,
|
||||
String minTemperature,
|
||||
String maxTemperature
|
||||
) {}
|
||||
|
|
@ -18,5 +18,7 @@ public interface StorageLocationRepository {
|
|||
|
||||
Result<RepositoryError, Boolean> existsByName(StorageLocationName name);
|
||||
|
||||
Result<RepositoryError, Boolean> existsByNameAndIdNot(StorageLocationName name, StorageLocationId id);
|
||||
|
||||
Result<RepositoryError, Void> save(StorageLocation location);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package de.effigenix.infrastructure.config;
|
||||
|
||||
import de.effigenix.application.inventory.ActivateStorageLocation;
|
||||
import de.effigenix.application.inventory.CreateStorageLocation;
|
||||
import de.effigenix.application.inventory.DeactivateStorageLocation;
|
||||
import de.effigenix.application.inventory.UpdateStorageLocation;
|
||||
import de.effigenix.domain.inventory.StorageLocationRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
@ -14,4 +17,19 @@ public class InventoryUseCaseConfiguration {
|
|||
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new CreateStorageLocation(storageLocationRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new UpdateStorageLocation(storageLocationRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new DeactivateStorageLocation(storageLocationRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new ActivateStorageLocation(storageLocationRepository);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,16 @@ public class JpaStorageLocationRepository implements StorageLocationRepository {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<RepositoryError, Boolean> existsByNameAndIdNot(StorageLocationName name, StorageLocationId id) {
|
||||
try {
|
||||
return Result.success(jpaRepository.existsByNameAndIdNot(name.value(), id.value()));
|
||||
} catch (Exception e) {
|
||||
logger.trace("Database error in existsByNameAndIdNot", e);
|
||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<RepositoryError, Void> save(StorageLocation location) {
|
||||
|
|
|
|||
|
|
@ -12,4 +12,6 @@ public interface StorageLocationJpaRepository extends JpaRepository<StorageLocat
|
|||
List<StorageLocationEntity> findByActiveTrue();
|
||||
|
||||
boolean existsByName(String name);
|
||||
|
||||
boolean existsByNameAndIdNot(String name, String id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package de.effigenix.infrastructure.inventory.web.controller;
|
||||
|
||||
import de.effigenix.application.inventory.ActivateStorageLocation;
|
||||
import de.effigenix.application.inventory.CreateStorageLocation;
|
||||
import de.effigenix.application.inventory.DeactivateStorageLocation;
|
||||
import de.effigenix.application.inventory.UpdateStorageLocation;
|
||||
import de.effigenix.application.inventory.command.CreateStorageLocationCommand;
|
||||
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||
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.security.ActorId;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
|
@ -26,9 +31,20 @@ public class StorageLocationController {
|
|||
private static final Logger logger = LoggerFactory.getLogger(StorageLocationController.class);
|
||||
|
||||
private final CreateStorageLocation createStorageLocation;
|
||||
private final UpdateStorageLocation updateStorageLocation;
|
||||
private final DeactivateStorageLocation deactivateStorageLocation;
|
||||
private final ActivateStorageLocation activateStorageLocation;
|
||||
|
||||
public StorageLocationController(CreateStorageLocation createStorageLocation) {
|
||||
public StorageLocationController(
|
||||
CreateStorageLocation createStorageLocation,
|
||||
UpdateStorageLocation updateStorageLocation,
|
||||
DeactivateStorageLocation deactivateStorageLocation,
|
||||
ActivateStorageLocation activateStorageLocation
|
||||
) {
|
||||
this.createStorageLocation = createStorageLocation;
|
||||
this.updateStorageLocation = updateStorageLocation;
|
||||
this.deactivateStorageLocation = deactivateStorageLocation;
|
||||
this.activateStorageLocation = activateStorageLocation;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
|
@ -55,6 +71,68 @@ public class StorageLocationController {
|
|||
.body(StorageLocationResponse.from(result.unsafeGetValue()));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||
public ResponseEntity<StorageLocationResponse> updateStorageLocation(
|
||||
@PathVariable("id") String storageLocationId,
|
||||
@RequestBody UpdateStorageLocationRequest request,
|
||||
Authentication authentication
|
||||
) {
|
||||
var actorId = extractActorId(authentication);
|
||||
logger.info("Updating storage location: {} by actor: {}", storageLocationId, actorId.value());
|
||||
|
||||
var cmd = new UpdateStorageLocationCommand(
|
||||
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()));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/deactivate")
|
||||
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||
public ResponseEntity<StorageLocationResponse> deactivateStorageLocation(
|
||||
@PathVariable("id") String storageLocationId,
|
||||
Authentication authentication
|
||||
) {
|
||||
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()));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/activate")
|
||||
@PreAuthorize("hasAuthority('STOCK_WRITE')")
|
||||
public ResponseEntity<StorageLocationResponse> activateStorageLocation(
|
||||
@PathVariable("id") String storageLocationId,
|
||||
Authentication authentication
|
||||
) {
|
||||
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()));
|
||||
}
|
||||
|
||||
private ActorId extractActorId(Authentication authentication) {
|
||||
if (authentication == null || authentication.getName() == null) {
|
||||
throw new IllegalStateException("No authentication found in SecurityContext");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package de.effigenix.infrastructure.inventory.web.dto;
|
||||
|
||||
public record UpdateStorageLocationRequest(
|
||||
String name,
|
||||
String minTemperature,
|
||||
String maxTemperature
|
||||
) {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue