1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 15:49:35 +01:00

feat(inventory): Bestandsparameter ändern (MinimumLevel, MinimumShelfLife)

Stock.update(StockUpdateDraft) ermöglicht optionale Aktualisierung von
MinimumLevel und MinimumShelfLife mit identischer Validierung wie create().
PUT /api/inventory/stocks/{id} Endpoint, UpdateStock Use Case + Tests.

Closes #9
This commit is contained in:
Sebastian Frick 2026-02-20 09:44:15 +01:00
parent 1c65ac7795
commit e8cbb948b7
10 changed files with 734 additions and 1 deletions

View file

@ -0,0 +1,57 @@
package de.effigenix.application.inventory;
import de.effigenix.application.inventory.command.UpdateStockCommand;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class UpdateStock {
private final StockRepository stockRepository;
public UpdateStock(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
public Result<StockError, Stock> execute(UpdateStockCommand cmd) {
// 1. Laden
StockId stockId;
try {
stockId = StockId.of(cmd.stockId());
} catch (IllegalArgumentException e) {
return Result.failure(new StockError.StockNotFound(cmd.stockId()));
}
Stock stock;
switch (stockRepository.findById(stockId)) {
case Result.Failure(var err) ->
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
case Result.Success(var opt) -> {
if (opt.isEmpty()) {
return Result.failure(new StockError.StockNotFound(cmd.stockId()));
}
stock = opt.get();
}
}
// 2. Draft bauen + Aggregate validieren lassen
var draft = new StockUpdateDraft(
cmd.minimumLevelAmount(), cmd.minimumLevelUnit(),
cmd.minimumShelfLifeDays()
);
switch (stock.update(draft)) {
case Result.Failure(var err) -> { return Result.failure(err); }
case Result.Success(var ignored) -> { }
}
// 3. Speichern
switch (stockRepository.save(stock)) {
case Result.Failure(var err) ->
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(stock);
}
}

View file

@ -0,0 +1,8 @@
package de.effigenix.application.inventory.command;
public record UpdateStockCommand(
String stockId,
String minimumLevelAmount,
String minimumLevelUnit,
Integer minimumShelfLifeDays
) {}

View file

@ -110,6 +110,33 @@ public class Stock {
return new Stock(id, articleId, storageLocationId, minimumLevel, minimumShelfLife, batches);
}
// ==================== Update ====================
/**
* Aktualisiert die Bestandsparameter (MinimumLevel, MinimumShelfLife).
* Null-Felder im Draft werden ignoriert (kein Update).
* Validierung identisch zu create().
*/
public Result<StockError, Void> update(StockUpdateDraft draft) {
// 1. MinimumLevel optional aktualisieren
if (draft.minimumLevelAmount() != null || draft.minimumLevelUnit() != null) {
switch (MinimumLevel.of(draft.minimumLevelAmount(), draft.minimumLevelUnit())) {
case Result.Failure(var err) -> { return Result.failure(err); }
case Result.Success(var val) -> this.minimumLevel = val;
}
}
// 2. MinimumShelfLife optional aktualisieren
if (draft.minimumShelfLifeDays() != null) {
switch (MinimumShelfLife.of(draft.minimumShelfLifeDays())) {
case Result.Failure(var err) -> { return Result.failure(err); }
case Result.Success(var val) -> this.minimumShelfLife = val;
}
}
return Result.success(null);
}
// ==================== Batch Management ====================
public Result<StockError, StockBatch> addBatch(StockBatchDraft draft) {

View file

@ -0,0 +1,16 @@
package de.effigenix.domain.inventory;
/**
* Rohe Eingabe zum Aktualisieren der Bestandsparameter eines Stock-Aggregates.
* Null-Felder bedeuten: Feld nicht ändern.
* Explizit gesetzte Werte werden validiert und übernommen.
*
* @param minimumLevelAmount Optional BigDecimal als String, nullable
* @param minimumLevelUnit Optional UnitOfMeasure als String, nullable
* @param minimumShelfLifeDays Optional Integer, nullable
*/
public record StockUpdateDraft(
String minimumLevelAmount,
String minimumLevelUnit,
Integer minimumShelfLifeDays
) {}

View file

@ -5,6 +5,7 @@ import de.effigenix.application.inventory.AddStockBatch;
import de.effigenix.application.inventory.BlockStockBatch;
import de.effigenix.application.inventory.CreateStock;
import de.effigenix.application.inventory.GetStock;
import de.effigenix.application.inventory.UpdateStock;
import de.effigenix.application.inventory.ListStocks;
import de.effigenix.application.inventory.RemoveStockBatch;
import de.effigenix.application.inventory.UnblockStockBatch;
@ -55,6 +56,11 @@ public class InventoryUseCaseConfiguration {
return new CreateStock(stockRepository);
}
@Bean
public UpdateStock updateStock(StockRepository stockRepository) {
return new UpdateStock(stockRepository);
}
@Bean
public GetStock getStock(StockRepository stockRepository) {
return new GetStock(stockRepository);

View file

@ -7,11 +7,13 @@ import de.effigenix.application.inventory.GetStock;
import de.effigenix.application.inventory.ListStocks;
import de.effigenix.application.inventory.RemoveStockBatch;
import de.effigenix.application.inventory.UnblockStockBatch;
import de.effigenix.application.inventory.UpdateStock;
import de.effigenix.application.inventory.command.AddStockBatchCommand;
import de.effigenix.application.inventory.command.BlockStockBatchCommand;
import de.effigenix.application.inventory.command.CreateStockCommand;
import de.effigenix.application.inventory.command.RemoveStockBatchCommand;
import de.effigenix.application.inventory.command.UnblockStockBatchCommand;
import de.effigenix.application.inventory.command.UpdateStockCommand;
import de.effigenix.domain.inventory.StockError;
import de.effigenix.shared.security.ActorId;
import de.effigenix.infrastructure.inventory.web.dto.AddStockBatchRequest;
@ -21,6 +23,7 @@ import de.effigenix.infrastructure.inventory.web.dto.CreateStockResponse;
import de.effigenix.infrastructure.inventory.web.dto.RemoveStockBatchRequest;
import de.effigenix.infrastructure.inventory.web.dto.StockBatchResponse;
import de.effigenix.infrastructure.inventory.web.dto.StockResponse;
import de.effigenix.infrastructure.inventory.web.dto.UpdateStockRequest;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
@ -43,6 +46,7 @@ public class StockController {
private static final Logger logger = LoggerFactory.getLogger(StockController.class);
private final CreateStock createStock;
private final UpdateStock updateStock;
private final GetStock getStock;
private final ListStocks listStocks;
private final AddStockBatch addStockBatch;
@ -50,10 +54,11 @@ public class StockController {
private final BlockStockBatch blockStockBatch;
private final UnblockStockBatch unblockStockBatch;
public StockController(CreateStock createStock, GetStock getStock, ListStocks listStocks,
public StockController(CreateStock createStock, UpdateStock updateStock, GetStock getStock, ListStocks listStocks,
AddStockBatch addStockBatch, RemoveStockBatch removeStockBatch,
BlockStockBatch blockStockBatch, UnblockStockBatch unblockStockBatch) {
this.createStock = createStock;
this.updateStock = updateStock;
this.getStock = getStock;
this.listStocks = listStocks;
this.addStockBatch = addStockBatch;
@ -117,6 +122,29 @@ public class StockController {
.body(CreateStockResponse.from(result.unsafeGetValue()));
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('STOCK_WRITE')")
public ResponseEntity<StockResponse> updateStock(
@PathVariable String id,
@Valid @RequestBody UpdateStockRequest request,
Authentication authentication
) {
logger.info("Updating stock: {} by actor: {}", id, authentication.getName());
var cmd = new UpdateStockCommand(
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()));
}
@PostMapping("/{stockId}/batches")
@PreAuthorize("hasAuthority('STOCK_WRITE')")
public ResponseEntity<StockBatchResponse> addBatch(

View file

@ -0,0 +1,7 @@
package de.effigenix.infrastructure.inventory.web.dto;
public record UpdateStockRequest(
String minimumLevelAmount,
String minimumLevelUnit,
Integer minimumShelfLifeDays
) {}