mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:09:35 +01:00
refactor(inventory): UnitOfWork-Pattern + JdbcClient-Migration
Letzter BC migriert: JPA/Hibernate durch JdbcClient ersetzt, @Transactional durch UnitOfWork-Pattern in allen schreibenden Use Cases. - 3 neue Jdbc-Repos: JdbcStorageLocationRepository, JdbcStockMovementRepository, JdbcStockRepository (4-Tabellen-Aggregat mit Batches, Reservations, Allocations) - 20 Use Cases angepasst (UoW für schreibende, @Transactional entfernt für lesende) - 15 alte JPA-Dateien gelöscht (6 Entities, 3 Mapper, 3 Adapter, 3 Spring Data Repos) - 9 Unit-Tests mit UoW-Mock-Pattern aktualisiert
This commit is contained in:
parent
c89ee359d1
commit
d4ac8cb1b9
48 changed files with 1024 additions and 1388 deletions
|
|
@ -2,16 +2,17 @@ package de.effigenix.application.inventory;
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class ActivateStorageLocation {
|
public class ActivateStorageLocation {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
this.storageLocationRepository = storageLocationRepository;
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||||
|
|
@ -36,12 +37,13 @@ public class ActivateStorageLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Speichern
|
// 3. Speichern
|
||||||
switch (storageLocationRepository.save(location)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (storageLocationRepository.save(location)) {
|
||||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(location);
|
return Result.success(location);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.AddStockBatchCommand;
|
import de.effigenix.application.inventory.command.AddStockBatchCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class AddStockBatch {
|
public class AddStockBatch {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public AddStockBatch(StockRepository stockRepository) {
|
public AddStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, StockBatch> execute(AddStockBatchCommand cmd) {
|
public Result<StockError, StockBatch> execute(AddStockBatchCommand cmd) {
|
||||||
|
|
@ -42,12 +43,13 @@ public class AddStockBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Stock speichern
|
// 3. Stock speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(batch);
|
return Result.success(batch);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,19 @@ import de.effigenix.application.usermanagement.AuditEvent;
|
||||||
import de.effigenix.application.usermanagement.AuditLogger;
|
import de.effigenix.application.usermanagement.AuditLogger;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class BlockStockBatch {
|
public class BlockStockBatch {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
private final AuditLogger auditLogger;
|
private final AuditLogger auditLogger;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public BlockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
public BlockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
this.auditLogger = auditLogger;
|
this.auditLogger = auditLogger;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Void> execute(BlockStockBatchCommand cmd, ActorId performedBy) {
|
public Result<StockError, Void> execute(BlockStockBatchCommand cmd, ActorId performedBy) {
|
||||||
|
|
@ -40,13 +41,14 @@ public class BlockStockBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Stock speichern
|
// 3. Stock speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
auditLogger.log(AuditEvent.STOCK_BATCH_BLOCKED, cmd.batchId(), "Reason: " + cmd.reason(), performedBy);
|
auditLogger.log(AuditEvent.STOCK_BATCH_BLOCKED, cmd.batchId(), "Reason: " + cmd.reason(), performedBy);
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@ import de.effigenix.application.inventory.command.CreateStockCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class CreateStock {
|
public class CreateStock {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public CreateStock(StockRepository stockRepository) {
|
public CreateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Stock> execute(CreateStockCommand cmd) {
|
public Result<StockError, Stock> execute(CreateStockCommand cmd) {
|
||||||
|
|
@ -43,17 +44,18 @@ public class CreateStock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Speichern (Race-Condition-Schutz: DuplicateEntry → DuplicateStock)
|
// 4. Speichern (Race-Condition-Schutz: DuplicateEntry → DuplicateStock)
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) -> {
|
switch (stockRepository.save(stock)) {
|
||||||
if (err instanceof RepositoryError.DuplicateEntry) {
|
case Result.Failure(var err) -> {
|
||||||
return Result.failure(new StockError.DuplicateStock(
|
if (err instanceof RepositoryError.DuplicateEntry) {
|
||||||
cmd.articleId(), cmd.storageLocationId()));
|
return Result.failure(new StockError.DuplicateStock(
|
||||||
|
cmd.articleId(), cmd.storageLocationId()));
|
||||||
|
}
|
||||||
|
return Result.failure(new StockError.RepositoryFailure(err.message()));
|
||||||
}
|
}
|
||||||
return Result.failure(new StockError.RepositoryFailure(err.message()));
|
case Result.Success(var ignored) -> { }
|
||||||
}
|
}
|
||||||
case Result.Success(var ignored) -> { }
|
return Result.success(stock);
|
||||||
}
|
});
|
||||||
|
|
||||||
return Result.success(stock);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.CreateStorageLocationCommand;
|
import de.effigenix.application.inventory.command.CreateStorageLocationCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class CreateStorageLocation {
|
public class CreateStorageLocation {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public CreateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public CreateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
this.storageLocationRepository = storageLocationRepository;
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StorageLocationError, StorageLocation> execute(CreateStorageLocationCommand cmd, ActorId performedBy) {
|
public Result<StorageLocationError, StorageLocation> execute(CreateStorageLocationCommand cmd, ActorId performedBy) {
|
||||||
|
|
@ -40,12 +41,13 @@ public class CreateStorageLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Speichern
|
// 4. Speichern
|
||||||
switch (storageLocationRepository.save(location)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (storageLocationRepository.save(location)) {
|
||||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(location);
|
return Result.success(location);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,19 @@ package de.effigenix.application.inventory;
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class DeactivateStorageLocation {
|
public class DeactivateStorageLocation {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public DeactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository) {
|
public DeactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.storageLocationRepository = storageLocationRepository;
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||||
|
|
@ -49,12 +50,13 @@ public class DeactivateStorageLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Speichern
|
// 4. Speichern
|
||||||
switch (storageLocationRepository.save(location)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (storageLocationRepository.save(location)) {
|
||||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(location);
|
return Result.success(location);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ import de.effigenix.domain.inventory.StockError;
|
||||||
import de.effigenix.domain.inventory.StockId;
|
import de.effigenix.domain.inventory.StockId;
|
||||||
import de.effigenix.domain.inventory.StockRepository;
|
import de.effigenix.domain.inventory.StockRepository;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class GetStock {
|
public class GetStock {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ import de.effigenix.domain.inventory.StockMovementRepository;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class GetStockMovement {
|
public class GetStockMovement {
|
||||||
|
|
||||||
private final StockMovementRepository stockMovementRepository;
|
private final StockMovementRepository stockMovementRepository;
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ import de.effigenix.domain.inventory.StorageLocationRepository;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class GetStorageLocation {
|
public class GetStorageLocation {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,10 @@ import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class ListStockMovements {
|
public class ListStockMovements {
|
||||||
|
|
||||||
private final StockMovementRepository stockMovementRepository;
|
private final StockMovementRepository stockMovementRepository;
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@ import de.effigenix.domain.inventory.StorageLocationId;
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
import de.effigenix.domain.masterdata.ArticleId;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class ListStocks {
|
public class ListStocks {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import de.effigenix.domain.inventory.StockRepository;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -21,7 +20,6 @@ public class ListStocksBelowMinimum {
|
||||||
this.authPort = authPort;
|
this.authPort = authPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<StockError, List<Stock>> execute(ActorId performedBy) {
|
public Result<StockError, List<Stock>> execute(ActorId performedBy) {
|
||||||
if (!authPort.can(performedBy, InventoryAction.STOCK_READ)) {
|
if (!authPort.can(performedBy, InventoryAction.STOCK_READ)) {
|
||||||
return Result.failure(new StockError.Unauthorized("Not authorized to list stocks below minimum"));
|
return Result.failure(new StockError.Unauthorized("Not authorized to list stocks below minimum"));
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class ListStorageLocations {
|
public class ListStorageLocations {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,20 @@ import de.effigenix.domain.inventory.StockMovementDraft;
|
||||||
import de.effigenix.domain.inventory.StockMovementError;
|
import de.effigenix.domain.inventory.StockMovementError;
|
||||||
import de.effigenix.domain.inventory.StockMovementRepository;
|
import de.effigenix.domain.inventory.StockMovementRepository;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class RecordStockMovement {
|
public class RecordStockMovement {
|
||||||
|
|
||||||
private final StockMovementRepository stockMovementRepository;
|
private final StockMovementRepository stockMovementRepository;
|
||||||
private final AuthorizationPort authPort;
|
private final AuthorizationPort authPort;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public RecordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authPort) {
|
public RecordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authPort, UnitOfWork unitOfWork) {
|
||||||
this.stockMovementRepository = stockMovementRepository;
|
this.stockMovementRepository = stockMovementRepository;
|
||||||
this.authPort = authPort;
|
this.authPort = authPort;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockMovementError, StockMovement> execute(RecordStockMovementCommand cmd, ActorId performedBy) {
|
public Result<StockMovementError, StockMovement> execute(RecordStockMovementCommand cmd, ActorId performedBy) {
|
||||||
|
|
@ -42,12 +43,13 @@ public class RecordStockMovement {
|
||||||
case Result.Success(var val) -> movement = val;
|
case Result.Success(var val) -> movement = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (stockMovementRepository.save(movement)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockMovementRepository.save(movement)) {
|
||||||
{ return Result.failure(new StockMovementError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockMovementError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(movement);
|
return Result.success(movement);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.ReleaseReservationCommand;
|
import de.effigenix.application.inventory.command.ReleaseReservationCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class ReleaseReservation {
|
public class ReleaseReservation {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public ReleaseReservation(StockRepository stockRepository) {
|
public ReleaseReservation(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Void> execute(ReleaseReservationCommand cmd) {
|
public Result<StockError, Void> execute(ReleaseReservationCommand cmd) {
|
||||||
|
|
@ -35,12 +36,13 @@ public class ReleaseReservation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Speichern
|
// 3. Speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,18 @@ import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Quantity;
|
import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class RemoveStockBatch {
|
public class RemoveStockBatch {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public RemoveStockBatch(StockRepository stockRepository) {
|
public RemoveStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Void> execute(RemoveStockBatchCommand cmd) {
|
public Result<StockError, Void> execute(RemoveStockBatchCommand cmd) {
|
||||||
|
|
@ -59,12 +60,13 @@ public class RemoveStockBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Stock speichern
|
// 4. Stock speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.ReserveStockCommand;
|
import de.effigenix.application.inventory.command.ReserveStockCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class ReserveStock {
|
public class ReserveStock {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public ReserveStock(StockRepository stockRepository) {
|
public ReserveStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Reservation> execute(ReserveStockCommand cmd) {
|
public Result<StockError, Reservation> execute(ReserveStockCommand cmd) {
|
||||||
|
|
@ -47,12 +48,13 @@ public class ReserveStock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Speichern
|
// 4. Speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(reservation);
|
return Result.success(reservation);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,21 @@ import de.effigenix.application.usermanagement.AuditEvent;
|
||||||
import de.effigenix.application.usermanagement.AuditLogger;
|
import de.effigenix.application.usermanagement.AuditLogger;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class UnblockStockBatch {
|
public class UnblockStockBatch {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
private final AuditLogger auditLogger;
|
private final AuditLogger auditLogger;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public UnblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
public UnblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
this.auditLogger = auditLogger;
|
this.auditLogger = auditLogger;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Void> execute(UnblockStockBatchCommand cmd, ActorId performedBy) {
|
public Result<StockError, Void> execute(UnblockStockBatchCommand cmd, ActorId performedBy) {
|
||||||
|
|
@ -42,13 +43,14 @@ public class UnblockStockBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Stock speichern
|
// 3. Stock speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
auditLogger.log(AuditEvent.STOCK_BATCH_UNBLOCKED, cmd.batchId(), performedBy);
|
auditLogger.log(AuditEvent.STOCK_BATCH_UNBLOCKED, cmd.batchId(), performedBy);
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.UpdateStockCommand;
|
import de.effigenix.application.inventory.command.UpdateStockCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class UpdateStock {
|
public class UpdateStock {
|
||||||
|
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public UpdateStock(StockRepository stockRepository) {
|
public UpdateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StockError, Stock> execute(UpdateStockCommand cmd) {
|
public Result<StockError, Stock> execute(UpdateStockCommand cmd) {
|
||||||
|
|
@ -46,12 +47,13 @@ public class UpdateStock {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Speichern
|
// 3. Speichern
|
||||||
switch (stockRepository.save(stock)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (stockRepository.save(stock)) {
|
||||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(stock);
|
return Result.success(stock);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ package de.effigenix.application.inventory;
|
||||||
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public class UpdateStorageLocation {
|
public class UpdateStorageLocation {
|
||||||
|
|
||||||
private final StorageLocationRepository storageLocationRepository;
|
private final StorageLocationRepository storageLocationRepository;
|
||||||
|
private final UnitOfWork unitOfWork;
|
||||||
|
|
||||||
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
this.storageLocationRepository = storageLocationRepository;
|
this.storageLocationRepository = storageLocationRepository;
|
||||||
|
this.unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<StorageLocationError, StorageLocation> execute(UpdateStorageLocationCommand cmd, ActorId performedBy) {
|
public Result<StorageLocationError, StorageLocation> execute(UpdateStorageLocationCommand cmd, ActorId performedBy) {
|
||||||
|
|
@ -51,12 +52,13 @@ public class UpdateStorageLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Speichern
|
// 4. Speichern
|
||||||
switch (storageLocationRepository.save(location)) {
|
return unitOfWork.executeAtomically(() -> {
|
||||||
case Result.Failure(var err) ->
|
switch (storageLocationRepository.save(location)) {
|
||||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
case Result.Failure(var err) ->
|
||||||
case Result.Success(var ignored) -> { }
|
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||||
}
|
case Result.Success(var ignored) -> { }
|
||||||
|
}
|
||||||
return Result.success(location);
|
return Result.success(location);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import de.effigenix.application.usermanagement.AuditLogger;
|
||||||
import de.effigenix.domain.inventory.StockMovementRepository;
|
import de.effigenix.domain.inventory.StockMovementRepository;
|
||||||
import de.effigenix.domain.inventory.StockRepository;
|
import de.effigenix.domain.inventory.StockRepository;
|
||||||
import de.effigenix.domain.inventory.StorageLocationRepository;
|
import de.effigenix.domain.inventory.StorageLocationRepository;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
@ -35,23 +36,23 @@ public class InventoryUseCaseConfiguration {
|
||||||
// ==================== StorageLocation Use Cases ====================
|
// ==================== StorageLocation Use Cases ====================
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
return new CreateStorageLocation(storageLocationRepository);
|
return new CreateStorageLocation(storageLocationRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
return new UpdateStorageLocation(storageLocationRepository);
|
return new UpdateStorageLocation(storageLocationRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository) {
|
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new DeactivateStorageLocation(storageLocationRepository, stockRepository);
|
return new DeactivateStorageLocation(storageLocationRepository, stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||||
return new ActivateStorageLocation(storageLocationRepository);
|
return new ActivateStorageLocation(storageLocationRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
@ -67,13 +68,13 @@ public class InventoryUseCaseConfiguration {
|
||||||
// ==================== Stock Use Cases ====================
|
// ==================== Stock Use Cases ====================
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CreateStock createStock(StockRepository stockRepository) {
|
public CreateStock createStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new CreateStock(stockRepository);
|
return new CreateStock(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UpdateStock updateStock(StockRepository stockRepository) {
|
public UpdateStock updateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new UpdateStock(stockRepository);
|
return new UpdateStock(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
@ -87,33 +88,33 @@ public class InventoryUseCaseConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AddStockBatch addStockBatch(StockRepository stockRepository) {
|
public AddStockBatch addStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new AddStockBatch(stockRepository);
|
return new AddStockBatch(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RemoveStockBatch removeStockBatch(StockRepository stockRepository) {
|
public RemoveStockBatch removeStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new RemoveStockBatch(stockRepository);
|
return new RemoveStockBatch(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public BlockStockBatch blockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
public BlockStockBatch blockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||||
return new BlockStockBatch(stockRepository, auditLogger);
|
return new BlockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UnblockStockBatch unblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
public UnblockStockBatch unblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||||
return new UnblockStockBatch(stockRepository, auditLogger);
|
return new UnblockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ReserveStock reserveStock(StockRepository stockRepository) {
|
public ReserveStock reserveStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new ReserveStock(stockRepository);
|
return new ReserveStock(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ReleaseReservation releaseReservation(StockRepository stockRepository) {
|
public ReleaseReservation releaseReservation(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||||
return new ReleaseReservation(stockRepository);
|
return new ReleaseReservation(stockRepository, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
@ -129,8 +130,8 @@ public class InventoryUseCaseConfiguration {
|
||||||
// ==================== StockMovement Use Cases ====================
|
// ==================== StockMovement Use Cases ====================
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RecordStockMovement recordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authorizationPort) {
|
public RecordStockMovement recordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authorizationPort, UnitOfWork unitOfWork) {
|
||||||
return new RecordStockMovement(stockMovementRepository, authorizationPort);
|
return new RecordStockMovement(stockMovementRepository, authorizationPort, unitOfWork);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "reservations")
|
|
||||||
public class ReservationEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "stock_id", nullable = false)
|
|
||||||
private StockEntity stock;
|
|
||||||
|
|
||||||
@Column(name = "reference_type", nullable = false, length = 30)
|
|
||||||
private String referenceType;
|
|
||||||
|
|
||||||
@Column(name = "reference_id", nullable = false, length = 100)
|
|
||||||
private String referenceId;
|
|
||||||
|
|
||||||
@Column(name = "quantity_amount", nullable = false, precision = 19, scale = 6)
|
|
||||||
private BigDecimal quantityAmount;
|
|
||||||
|
|
||||||
@Column(name = "quantity_unit", nullable = false, length = 20)
|
|
||||||
private String quantityUnit;
|
|
||||||
|
|
||||||
@Column(name = "priority", nullable = false, length = 20)
|
|
||||||
private String priority;
|
|
||||||
|
|
||||||
@Column(name = "reserved_at", nullable = false)
|
|
||||||
private Instant reservedAt;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "reservation", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
private List<StockBatchAllocationEntity> allocations = new ArrayList<>();
|
|
||||||
|
|
||||||
protected ReservationEntity() {}
|
|
||||||
|
|
||||||
public ReservationEntity(String id, StockEntity stock, String referenceType, String referenceId,
|
|
||||||
BigDecimal quantityAmount, String quantityUnit, String priority,
|
|
||||||
Instant reservedAt) {
|
|
||||||
this.id = id;
|
|
||||||
this.stock = stock;
|
|
||||||
this.referenceType = referenceType;
|
|
||||||
this.referenceId = referenceId;
|
|
||||||
this.quantityAmount = quantityAmount;
|
|
||||||
this.quantityUnit = quantityUnit;
|
|
||||||
this.priority = priority;
|
|
||||||
this.reservedAt = reservedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
public StockEntity getStock() { return stock; }
|
|
||||||
public void setStock(StockEntity stock) { this.stock = stock; }
|
|
||||||
public String getReferenceType() { return referenceType; }
|
|
||||||
public void setReferenceType(String referenceType) { this.referenceType = referenceType; }
|
|
||||||
public String getReferenceId() { return referenceId; }
|
|
||||||
public void setReferenceId(String referenceId) { this.referenceId = referenceId; }
|
|
||||||
public BigDecimal getQuantityAmount() { return quantityAmount; }
|
|
||||||
public void setQuantityAmount(BigDecimal quantityAmount) { this.quantityAmount = quantityAmount; }
|
|
||||||
public String getQuantityUnit() { return quantityUnit; }
|
|
||||||
public void setQuantityUnit(String quantityUnit) { this.quantityUnit = quantityUnit; }
|
|
||||||
public String getPriority() { return priority; }
|
|
||||||
public void setPriority(String priority) { this.priority = priority; }
|
|
||||||
public Instant getReservedAt() { return reservedAt; }
|
|
||||||
public void setReservedAt(Instant reservedAt) { this.reservedAt = reservedAt; }
|
|
||||||
public List<StockBatchAllocationEntity> getAllocations() { return allocations; }
|
|
||||||
public void setAllocations(List<StockBatchAllocationEntity> allocations) { this.allocations = allocations; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "stock_batch_allocations")
|
|
||||||
public class StockBatchAllocationEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "reservation_id", nullable = false)
|
|
||||||
private ReservationEntity reservation;
|
|
||||||
|
|
||||||
@Column(name = "stock_batch_id", nullable = false, length = 36)
|
|
||||||
private String stockBatchId;
|
|
||||||
|
|
||||||
@Column(name = "allocated_quantity_amount", nullable = false, precision = 19, scale = 6)
|
|
||||||
private BigDecimal allocatedQuantityAmount;
|
|
||||||
|
|
||||||
@Column(name = "allocated_quantity_unit", nullable = false, length = 20)
|
|
||||||
private String allocatedQuantityUnit;
|
|
||||||
|
|
||||||
protected StockBatchAllocationEntity() {}
|
|
||||||
|
|
||||||
public StockBatchAllocationEntity(String id, ReservationEntity reservation, String stockBatchId,
|
|
||||||
BigDecimal allocatedQuantityAmount, String allocatedQuantityUnit) {
|
|
||||||
this.id = id;
|
|
||||||
this.reservation = reservation;
|
|
||||||
this.stockBatchId = stockBatchId;
|
|
||||||
this.allocatedQuantityAmount = allocatedQuantityAmount;
|
|
||||||
this.allocatedQuantityUnit = allocatedQuantityUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
public ReservationEntity getReservation() { return reservation; }
|
|
||||||
public void setReservation(ReservationEntity reservation) { this.reservation = reservation; }
|
|
||||||
public String getStockBatchId() { return stockBatchId; }
|
|
||||||
public void setStockBatchId(String stockBatchId) { this.stockBatchId = stockBatchId; }
|
|
||||||
public BigDecimal getAllocatedQuantityAmount() { return allocatedQuantityAmount; }
|
|
||||||
public void setAllocatedQuantityAmount(BigDecimal allocatedQuantityAmount) { this.allocatedQuantityAmount = allocatedQuantityAmount; }
|
|
||||||
public String getAllocatedQuantityUnit() { return allocatedQuantityUnit; }
|
|
||||||
public void setAllocatedQuantityUnit(String allocatedQuantityUnit) { this.allocatedQuantityUnit = allocatedQuantityUnit; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "stock_batches")
|
|
||||||
public class StockBatchEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "stock_id", nullable = false)
|
|
||||||
private StockEntity stock;
|
|
||||||
|
|
||||||
@Column(name = "batch_id", nullable = false, length = 100)
|
|
||||||
private String batchId;
|
|
||||||
|
|
||||||
@Column(name = "batch_type", nullable = false, length = 20)
|
|
||||||
private String batchType;
|
|
||||||
|
|
||||||
@Column(name = "quantity_amount", nullable = false, precision = 19, scale = 6)
|
|
||||||
private BigDecimal quantityAmount;
|
|
||||||
|
|
||||||
@Column(name = "quantity_unit", nullable = false, length = 20)
|
|
||||||
private String quantityUnit;
|
|
||||||
|
|
||||||
@Column(name = "expiry_date", nullable = false)
|
|
||||||
private LocalDate expiryDate;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = 20)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Column(name = "received_at", nullable = false)
|
|
||||||
private Instant receivedAt;
|
|
||||||
|
|
||||||
protected StockBatchEntity() {}
|
|
||||||
|
|
||||||
public StockBatchEntity(String id, StockEntity stock, String batchId, String batchType,
|
|
||||||
BigDecimal quantityAmount, String quantityUnit, LocalDate expiryDate,
|
|
||||||
String status, Instant receivedAt) {
|
|
||||||
this.id = id;
|
|
||||||
this.stock = stock;
|
|
||||||
this.batchId = batchId;
|
|
||||||
this.batchType = batchType;
|
|
||||||
this.quantityAmount = quantityAmount;
|
|
||||||
this.quantityUnit = quantityUnit;
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
this.status = status;
|
|
||||||
this.receivedAt = receivedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public StockEntity getStock() { return stock; }
|
|
||||||
public String getBatchId() { return batchId; }
|
|
||||||
public String getBatchType() { return batchType; }
|
|
||||||
public BigDecimal getQuantityAmount() { return quantityAmount; }
|
|
||||||
public String getQuantityUnit() { return quantityUnit; }
|
|
||||||
public LocalDate getExpiryDate() { return expiryDate; }
|
|
||||||
public String getStatus() { return status; }
|
|
||||||
public Instant getReceivedAt() { return receivedAt; }
|
|
||||||
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
public void setStock(StockEntity stock) { this.stock = stock; }
|
|
||||||
public void setBatchId(String batchId) { this.batchId = batchId; }
|
|
||||||
public void setBatchType(String batchType) { this.batchType = batchType; }
|
|
||||||
public void setQuantityAmount(BigDecimal quantityAmount) { this.quantityAmount = quantityAmount; }
|
|
||||||
public void setQuantityUnit(String quantityUnit) { this.quantityUnit = quantityUnit; }
|
|
||||||
public void setExpiryDate(LocalDate expiryDate) { this.expiryDate = expiryDate; }
|
|
||||||
public void setStatus(String status) { this.status = status; }
|
|
||||||
public void setReceivedAt(Instant receivedAt) { this.receivedAt = receivedAt; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "stocks")
|
|
||||||
public class StockEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Column(name = "article_id", nullable = false, length = 36)
|
|
||||||
private String articleId;
|
|
||||||
|
|
||||||
@Column(name = "storage_location_id", nullable = false, length = 36)
|
|
||||||
private String storageLocationId;
|
|
||||||
|
|
||||||
@Column(name = "minimum_level_amount", precision = 12, scale = 3)
|
|
||||||
private BigDecimal minimumLevelAmount;
|
|
||||||
|
|
||||||
@Column(name = "minimum_level_unit", length = 20)
|
|
||||||
private String minimumLevelUnit;
|
|
||||||
|
|
||||||
@Column(name = "minimum_shelf_life_days")
|
|
||||||
private Integer minimumShelfLifeDays;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
|
|
||||||
private List<StockBatchEntity> batches = new ArrayList<>();
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
private List<ReservationEntity> reservations = new ArrayList<>();
|
|
||||||
|
|
||||||
public StockEntity() {}
|
|
||||||
|
|
||||||
// ==================== Getters & Setters ====================
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
|
|
||||||
public String getArticleId() { return articleId; }
|
|
||||||
public void setArticleId(String articleId) { this.articleId = articleId; }
|
|
||||||
|
|
||||||
public String getStorageLocationId() { return storageLocationId; }
|
|
||||||
public void setStorageLocationId(String storageLocationId) { this.storageLocationId = storageLocationId; }
|
|
||||||
|
|
||||||
public BigDecimal getMinimumLevelAmount() { return minimumLevelAmount; }
|
|
||||||
public void setMinimumLevelAmount(BigDecimal minimumLevelAmount) { this.minimumLevelAmount = minimumLevelAmount; }
|
|
||||||
|
|
||||||
public String getMinimumLevelUnit() { return minimumLevelUnit; }
|
|
||||||
public void setMinimumLevelUnit(String minimumLevelUnit) { this.minimumLevelUnit = minimumLevelUnit; }
|
|
||||||
|
|
||||||
public Integer getMinimumShelfLifeDays() { return minimumShelfLifeDays; }
|
|
||||||
public void setMinimumShelfLifeDays(Integer minimumShelfLifeDays) { this.minimumShelfLifeDays = minimumShelfLifeDays; }
|
|
||||||
|
|
||||||
public List<StockBatchEntity> getBatches() { return batches; }
|
|
||||||
public void setBatches(List<StockBatchEntity> batches) { this.batches = batches; }
|
|
||||||
|
|
||||||
public List<ReservationEntity> getReservations() { return reservations; }
|
|
||||||
public void setReservations(List<ReservationEntity> reservations) { this.reservations = reservations; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "stock_movements")
|
|
||||||
public class StockMovementEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Column(name = "stock_id", nullable = false, length = 36)
|
|
||||||
private String stockId;
|
|
||||||
|
|
||||||
@Column(name = "article_id", nullable = false, length = 36)
|
|
||||||
private String articleId;
|
|
||||||
|
|
||||||
@Column(name = "stock_batch_id", nullable = false, length = 36)
|
|
||||||
private String stockBatchId;
|
|
||||||
|
|
||||||
@Column(name = "batch_id", nullable = false, length = 100)
|
|
||||||
private String batchId;
|
|
||||||
|
|
||||||
@Column(name = "batch_type", nullable = false, length = 20)
|
|
||||||
private String batchType;
|
|
||||||
|
|
||||||
@Column(name = "movement_type", nullable = false, length = 30)
|
|
||||||
private String movementType;
|
|
||||||
|
|
||||||
@Column(name = "direction", nullable = false, length = 10)
|
|
||||||
private String direction;
|
|
||||||
|
|
||||||
@Column(name = "quantity_amount", nullable = false, precision = 19, scale = 6)
|
|
||||||
private BigDecimal quantityAmount;
|
|
||||||
|
|
||||||
@Column(name = "quantity_unit", nullable = false, length = 20)
|
|
||||||
private String quantityUnit;
|
|
||||||
|
|
||||||
@Column(name = "reason", length = 500)
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
@Column(name = "reference_document_id", length = 100)
|
|
||||||
private String referenceDocumentId;
|
|
||||||
|
|
||||||
@Column(name = "performed_by", nullable = false, length = 36)
|
|
||||||
private String performedBy;
|
|
||||||
|
|
||||||
@Column(name = "performed_at", nullable = false)
|
|
||||||
private Instant performedAt;
|
|
||||||
|
|
||||||
public StockMovementEntity() {}
|
|
||||||
|
|
||||||
public StockMovementEntity(String id, String stockId, String articleId, String stockBatchId,
|
|
||||||
String batchId, String batchType, String movementType, String direction,
|
|
||||||
BigDecimal quantityAmount, String quantityUnit, String reason,
|
|
||||||
String referenceDocumentId, String performedBy, Instant performedAt) {
|
|
||||||
this.id = id;
|
|
||||||
this.stockId = stockId;
|
|
||||||
this.articleId = articleId;
|
|
||||||
this.stockBatchId = stockBatchId;
|
|
||||||
this.batchId = batchId;
|
|
||||||
this.batchType = batchType;
|
|
||||||
this.movementType = movementType;
|
|
||||||
this.direction = direction;
|
|
||||||
this.quantityAmount = quantityAmount;
|
|
||||||
this.quantityUnit = quantityUnit;
|
|
||||||
this.reason = reason;
|
|
||||||
this.referenceDocumentId = referenceDocumentId;
|
|
||||||
this.performedBy = performedBy;
|
|
||||||
this.performedAt = performedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Getters & Setters ====================
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
|
|
||||||
public String getStockId() { return stockId; }
|
|
||||||
public void setStockId(String stockId) { this.stockId = stockId; }
|
|
||||||
|
|
||||||
public String getArticleId() { return articleId; }
|
|
||||||
public void setArticleId(String articleId) { this.articleId = articleId; }
|
|
||||||
|
|
||||||
public String getStockBatchId() { return stockBatchId; }
|
|
||||||
public void setStockBatchId(String stockBatchId) { this.stockBatchId = stockBatchId; }
|
|
||||||
|
|
||||||
public String getBatchId() { return batchId; }
|
|
||||||
public void setBatchId(String batchId) { this.batchId = batchId; }
|
|
||||||
|
|
||||||
public String getBatchType() { return batchType; }
|
|
||||||
public void setBatchType(String batchType) { this.batchType = batchType; }
|
|
||||||
|
|
||||||
public String getMovementType() { return movementType; }
|
|
||||||
public void setMovementType(String movementType) { this.movementType = movementType; }
|
|
||||||
|
|
||||||
public String getDirection() { return direction; }
|
|
||||||
public void setDirection(String direction) { this.direction = direction; }
|
|
||||||
|
|
||||||
public BigDecimal getQuantityAmount() { return quantityAmount; }
|
|
||||||
public void setQuantityAmount(BigDecimal quantityAmount) { this.quantityAmount = quantityAmount; }
|
|
||||||
|
|
||||||
public String getQuantityUnit() { return quantityUnit; }
|
|
||||||
public void setQuantityUnit(String quantityUnit) { this.quantityUnit = quantityUnit; }
|
|
||||||
|
|
||||||
public String getReason() { return reason; }
|
|
||||||
public void setReason(String reason) { this.reason = reason; }
|
|
||||||
|
|
||||||
public String getReferenceDocumentId() { return referenceDocumentId; }
|
|
||||||
public void setReferenceDocumentId(String referenceDocumentId) { this.referenceDocumentId = referenceDocumentId; }
|
|
||||||
|
|
||||||
public String getPerformedBy() { return performedBy; }
|
|
||||||
public void setPerformedBy(String performedBy) { this.performedBy = performedBy; }
|
|
||||||
|
|
||||||
public Instant getPerformedAt() { return performedAt; }
|
|
||||||
public void setPerformedAt(Instant performedAt) { this.performedAt = performedAt; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "storage_locations")
|
|
||||||
public class StorageLocationEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(name = "id", nullable = false, length = 36)
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@Column(name = "name", nullable = false, unique = true, length = 100)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Column(name = "storage_type", nullable = false, length = 30)
|
|
||||||
private String storageType;
|
|
||||||
|
|
||||||
@Column(name = "min_temperature", precision = 5, scale = 1)
|
|
||||||
private BigDecimal minTemperature;
|
|
||||||
|
|
||||||
@Column(name = "max_temperature", precision = 5, scale = 1)
|
|
||||||
private BigDecimal maxTemperature;
|
|
||||||
|
|
||||||
@Column(name = "active", nullable = false)
|
|
||||||
private boolean active;
|
|
||||||
|
|
||||||
public StorageLocationEntity() {}
|
|
||||||
|
|
||||||
// ==================== Getters & Setters ====================
|
|
||||||
|
|
||||||
public String getId() { return id; }
|
|
||||||
public void setId(String id) { this.id = id; }
|
|
||||||
|
|
||||||
public String getName() { return name; }
|
|
||||||
public void setName(String name) { this.name = name; }
|
|
||||||
|
|
||||||
public String getStorageType() { return storageType; }
|
|
||||||
public void setStorageType(String storageType) { this.storageType = storageType; }
|
|
||||||
|
|
||||||
public BigDecimal getMinTemperature() { return minTemperature; }
|
|
||||||
public void setMinTemperature(BigDecimal minTemperature) { this.minTemperature = minTemperature; }
|
|
||||||
|
|
||||||
public BigDecimal getMaxTemperature() { return maxTemperature; }
|
|
||||||
public void setMaxTemperature(BigDecimal maxTemperature) { this.maxTemperature = maxTemperature; }
|
|
||||||
|
|
||||||
public boolean isActive() { return active; }
|
|
||||||
public void setActive(boolean active) { this.active = active; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.mapper;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
|
||||||
import de.effigenix.shared.common.Quantity;
|
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.ReservationEntity;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockBatchAllocationEntity;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockBatchEntity;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockEntity;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class StockMapper {
|
|
||||||
|
|
||||||
public StockEntity toEntity(Stock stock) {
|
|
||||||
var entity = new StockEntity();
|
|
||||||
entity.setId(stock.id().value());
|
|
||||||
entity.setArticleId(stock.articleId().value());
|
|
||||||
entity.setStorageLocationId(stock.storageLocationId().value());
|
|
||||||
|
|
||||||
if (stock.minimumLevel() != null) {
|
|
||||||
entity.setMinimumLevelAmount(stock.minimumLevel().quantity().amount());
|
|
||||||
entity.setMinimumLevelUnit(stock.minimumLevel().quantity().uom().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stock.minimumShelfLife() != null) {
|
|
||||||
entity.setMinimumShelfLifeDays(stock.minimumShelfLife().days());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<StockBatchEntity> batchEntities = stock.batches().stream()
|
|
||||||
.map(b -> toBatchEntity(b, entity))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
entity.setBatches(batchEntities);
|
|
||||||
|
|
||||||
List<ReservationEntity> reservationEntities = stock.reservations().stream()
|
|
||||||
.map(r -> toReservationEntity(r, entity))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
entity.setReservations(reservationEntities);
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stock toDomain(StockEntity entity) {
|
|
||||||
MinimumLevel minimumLevel = null;
|
|
||||||
if (entity.getMinimumLevelAmount() != null && entity.getMinimumLevelUnit() != null) {
|
|
||||||
var quantity = Quantity.reconstitute(
|
|
||||||
entity.getMinimumLevelAmount(),
|
|
||||||
UnitOfMeasure.valueOf(entity.getMinimumLevelUnit())
|
|
||||||
);
|
|
||||||
minimumLevel = new MinimumLevel(quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
MinimumShelfLife minimumShelfLife = null;
|
|
||||||
if (entity.getMinimumShelfLifeDays() != null) {
|
|
||||||
minimumShelfLife = new MinimumShelfLife(entity.getMinimumShelfLifeDays());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<StockBatch> batches = entity.getBatches().stream()
|
|
||||||
.map(this::toDomainBatch)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
List<Reservation> reservations = entity.getReservations().stream()
|
|
||||||
.map(this::toDomainReservation)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return Stock.reconstitute(
|
|
||||||
StockId.of(entity.getId()),
|
|
||||||
ArticleId.of(entity.getArticleId()),
|
|
||||||
StorageLocationId.of(entity.getStorageLocationId()),
|
|
||||||
minimumLevel,
|
|
||||||
minimumShelfLife,
|
|
||||||
batches,
|
|
||||||
reservations
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StockBatchEntity toBatchEntity(StockBatch batch, StockEntity stockEntity) {
|
|
||||||
return new StockBatchEntity(
|
|
||||||
batch.id().value(),
|
|
||||||
stockEntity,
|
|
||||||
batch.batchReference().batchId(),
|
|
||||||
batch.batchReference().batchType().name(),
|
|
||||||
batch.quantity().amount(),
|
|
||||||
batch.quantity().uom().name(),
|
|
||||||
batch.expiryDate(),
|
|
||||||
batch.status().name(),
|
|
||||||
batch.receivedAt()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StockBatch toDomainBatch(StockBatchEntity entity) {
|
|
||||||
return StockBatch.reconstitute(
|
|
||||||
StockBatchId.of(entity.getId()),
|
|
||||||
new BatchReference(entity.getBatchId(), BatchType.valueOf(entity.getBatchType())),
|
|
||||||
Quantity.reconstitute(
|
|
||||||
entity.getQuantityAmount(),
|
|
||||||
UnitOfMeasure.valueOf(entity.getQuantityUnit())
|
|
||||||
),
|
|
||||||
entity.getExpiryDate(),
|
|
||||||
StockBatchStatus.valueOf(entity.getStatus()),
|
|
||||||
entity.getReceivedAt()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReservationEntity toReservationEntity(Reservation reservation, StockEntity stockEntity) {
|
|
||||||
var entity = new ReservationEntity(
|
|
||||||
reservation.id().value(),
|
|
||||||
stockEntity,
|
|
||||||
reservation.referenceType().name(),
|
|
||||||
reservation.referenceId(),
|
|
||||||
reservation.quantity().amount(),
|
|
||||||
reservation.quantity().uom().name(),
|
|
||||||
reservation.priority().name(),
|
|
||||||
reservation.reservedAt()
|
|
||||||
);
|
|
||||||
|
|
||||||
List<StockBatchAllocationEntity> allocationEntities = reservation.allocations().stream()
|
|
||||||
.map(a -> toAllocationEntity(a, entity))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
entity.setAllocations(allocationEntities);
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private StockBatchAllocationEntity toAllocationEntity(StockBatchAllocation allocation, ReservationEntity reservationEntity) {
|
|
||||||
return new StockBatchAllocationEntity(
|
|
||||||
allocation.id().value(),
|
|
||||||
reservationEntity,
|
|
||||||
allocation.stockBatchId().value(),
|
|
||||||
allocation.allocatedQuantity().amount(),
|
|
||||||
allocation.allocatedQuantity().uom().name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Reservation toDomainReservation(ReservationEntity entity) {
|
|
||||||
List<StockBatchAllocation> allocations = entity.getAllocations().stream()
|
|
||||||
.map(this::toDomainAllocation)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return new Reservation(
|
|
||||||
ReservationId.of(entity.getId()),
|
|
||||||
ReferenceType.valueOf(entity.getReferenceType()),
|
|
||||||
entity.getReferenceId(),
|
|
||||||
Quantity.reconstitute(
|
|
||||||
entity.getQuantityAmount(),
|
|
||||||
UnitOfMeasure.valueOf(entity.getQuantityUnit())
|
|
||||||
),
|
|
||||||
ReservationPriority.valueOf(entity.getPriority()),
|
|
||||||
entity.getReservedAt(),
|
|
||||||
allocations
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StockBatchAllocation toDomainAllocation(StockBatchAllocationEntity entity) {
|
|
||||||
return new StockBatchAllocation(
|
|
||||||
AllocationId.of(entity.getId()),
|
|
||||||
StockBatchId.of(entity.getStockBatchId()),
|
|
||||||
Quantity.reconstitute(
|
|
||||||
entity.getAllocatedQuantityAmount(),
|
|
||||||
UnitOfMeasure.valueOf(entity.getAllocatedQuantityUnit())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.mapper;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
|
||||||
import de.effigenix.shared.common.Quantity;
|
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockMovementEntity;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class StockMovementMapper {
|
|
||||||
|
|
||||||
public StockMovementEntity toEntity(StockMovement movement) {
|
|
||||||
return new StockMovementEntity(
|
|
||||||
movement.id().value(),
|
|
||||||
movement.stockId().value(),
|
|
||||||
movement.articleId().value(),
|
|
||||||
movement.stockBatchId().value(),
|
|
||||||
movement.batchReference().batchId(),
|
|
||||||
movement.batchReference().batchType().name(),
|
|
||||||
movement.movementType().name(),
|
|
||||||
movement.direction().name(),
|
|
||||||
movement.quantity().amount(),
|
|
||||||
movement.quantity().uom().name(),
|
|
||||||
movement.reason(),
|
|
||||||
movement.referenceDocumentId(),
|
|
||||||
movement.performedBy(),
|
|
||||||
movement.performedAt()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StockMovement toDomain(StockMovementEntity entity) {
|
|
||||||
return StockMovement.reconstitute(
|
|
||||||
StockMovementId.of(entity.getId()),
|
|
||||||
StockId.of(entity.getStockId()),
|
|
||||||
ArticleId.of(entity.getArticleId()),
|
|
||||||
StockBatchId.of(entity.getStockBatchId()),
|
|
||||||
new BatchReference(entity.getBatchId(), BatchType.valueOf(entity.getBatchType())),
|
|
||||||
MovementType.valueOf(entity.getMovementType()),
|
|
||||||
MovementDirection.valueOf(entity.getDirection()),
|
|
||||||
Quantity.reconstitute(
|
|
||||||
entity.getQuantityAmount(),
|
|
||||||
UnitOfMeasure.valueOf(entity.getQuantityUnit())
|
|
||||||
),
|
|
||||||
entity.getReason(),
|
|
||||||
entity.getReferenceDocumentId(),
|
|
||||||
entity.getPerformedBy(),
|
|
||||||
entity.getPerformedAt()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.mapper;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StorageLocationEntity;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class StorageLocationMapper {
|
|
||||||
|
|
||||||
public StorageLocationEntity toEntity(StorageLocation location) {
|
|
||||||
var entity = new StorageLocationEntity();
|
|
||||||
entity.setId(location.id().value());
|
|
||||||
entity.setName(location.name().value());
|
|
||||||
entity.setStorageType(location.storageType().name());
|
|
||||||
entity.setActive(location.active());
|
|
||||||
|
|
||||||
var range = location.temperatureRange();
|
|
||||||
if (range != null) {
|
|
||||||
entity.setMinTemperature(range.minTemperature());
|
|
||||||
entity.setMaxTemperature(range.maxTemperature());
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StorageLocation toDomain(StorageLocationEntity entity) {
|
|
||||||
TemperatureRange temperatureRange = null;
|
|
||||||
if (entity.getMinTemperature() != null && entity.getMaxTemperature() != null) {
|
|
||||||
temperatureRange = new TemperatureRange(entity.getMinTemperature(), entity.getMaxTemperature());
|
|
||||||
}
|
|
||||||
|
|
||||||
return StorageLocation.reconstitute(
|
|
||||||
StorageLocationId.of(entity.getId()),
|
|
||||||
new StorageLocationName(entity.getName()),
|
|
||||||
StorageType.valueOf(entity.getStorageType()),
|
|
||||||
temperatureRange,
|
|
||||||
entity.isActive()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
package de.effigenix.infrastructure.inventory.persistence.repository;
|
||||||
|
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.domain.masterdata.ArticleId;
|
||||||
|
import de.effigenix.shared.common.Quantity;
|
||||||
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@Profile("!no-db")
|
||||||
|
public class JdbcStockMovementRepository implements StockMovementRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JdbcStockMovementRepository.class);
|
||||||
|
|
||||||
|
private final JdbcClient jdbc;
|
||||||
|
|
||||||
|
public JdbcStockMovementRepository(JdbcClient jdbc) {
|
||||||
|
this.jdbc = jdbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Optional<StockMovement>> findById(StockMovementId id) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE id = :id")
|
||||||
|
.param("id", id.value())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.optional();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAll() {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements ORDER BY performed_at DESC")
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByStockId(StockId stockId) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE stock_id = :stockId ORDER BY performed_at DESC")
|
||||||
|
.param("stockId", stockId.value())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByStockId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByArticleId(ArticleId articleId) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE article_id = :articleId ORDER BY performed_at DESC")
|
||||||
|
.param("articleId", articleId.value())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByArticleId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByMovementType(MovementType movementType) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE movement_type = :movementType ORDER BY performed_at DESC")
|
||||||
|
.param("movementType", movementType.name())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByMovementType", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByBatchReference(String batchReference) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE batch_id = :batchId ORDER BY performed_at DESC")
|
||||||
|
.param("batchId", batchReference)
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByBatchReference", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtBetween(Instant from, Instant to) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE performed_at >= :from AND performed_at <= :to ORDER BY performed_at DESC")
|
||||||
|
.param("from", from)
|
||||||
|
.param("to", to)
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByPerformedAtBetween", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtAfter(Instant from) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE performed_at >= :from ORDER BY performed_at DESC")
|
||||||
|
.param("from", from)
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByPerformedAtAfter", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtBefore(Instant to) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM stock_movements WHERE performed_at <= :to ORDER BY performed_at DESC")
|
||||||
|
.param("to", to)
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByPerformedAtBefore", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Void> save(StockMovement movement) {
|
||||||
|
try {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO stock_movements
|
||||||
|
(id, stock_id, article_id, stock_batch_id, batch_id, batch_type,
|
||||||
|
movement_type, direction, quantity_amount, quantity_unit,
|
||||||
|
reason, reference_document_id, performed_by, performed_at)
|
||||||
|
VALUES (:id, :stockId, :articleId, :stockBatchId, :batchId, :batchType,
|
||||||
|
:movementType, :direction, :quantityAmount, :quantityUnit,
|
||||||
|
:reason, :referenceDocumentId, :performedBy, :performedAt)
|
||||||
|
""")
|
||||||
|
.param("id", movement.id().value())
|
||||||
|
.param("stockId", movement.stockId().value())
|
||||||
|
.param("articleId", movement.articleId().value())
|
||||||
|
.param("stockBatchId", movement.stockBatchId().value())
|
||||||
|
.param("batchId", movement.batchReference().batchId())
|
||||||
|
.param("batchType", movement.batchReference().batchType().name())
|
||||||
|
.param("movementType", movement.movementType().name())
|
||||||
|
.param("direction", movement.direction().name())
|
||||||
|
.param("quantityAmount", movement.quantity().amount())
|
||||||
|
.param("quantityUnit", movement.quantity().uom().name())
|
||||||
|
.param("reason", movement.reason())
|
||||||
|
.param("referenceDocumentId", movement.referenceDocumentId())
|
||||||
|
.param("performedBy", movement.performedBy())
|
||||||
|
.param("performedAt", movement.performedAt())
|
||||||
|
.update();
|
||||||
|
|
||||||
|
return Result.success(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StockMovement mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
return StockMovement.reconstitute(
|
||||||
|
StockMovementId.of(rs.getString("id")),
|
||||||
|
StockId.of(rs.getString("stock_id")),
|
||||||
|
ArticleId.of(rs.getString("article_id")),
|
||||||
|
StockBatchId.of(rs.getString("stock_batch_id")),
|
||||||
|
new BatchReference(rs.getString("batch_id"), BatchType.valueOf(rs.getString("batch_type"))),
|
||||||
|
MovementType.valueOf(rs.getString("movement_type")),
|
||||||
|
MovementDirection.valueOf(rs.getString("direction")),
|
||||||
|
Quantity.reconstitute(
|
||||||
|
rs.getBigDecimal("quantity_amount"),
|
||||||
|
UnitOfMeasure.valueOf(rs.getString("quantity_unit"))
|
||||||
|
),
|
||||||
|
rs.getString("reason"),
|
||||||
|
rs.getString("reference_document_id"),
|
||||||
|
rs.getString("performed_by"),
|
||||||
|
rs.getObject("performed_at", Instant.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,413 @@
|
||||||
|
package de.effigenix.infrastructure.inventory.persistence.repository;
|
||||||
|
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.domain.masterdata.ArticleId;
|
||||||
|
import de.effigenix.shared.common.Quantity;
|
||||||
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@Profile("!no-db")
|
||||||
|
public class JdbcStockRepository implements StockRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JdbcStockRepository.class);
|
||||||
|
|
||||||
|
private final JdbcClient jdbc;
|
||||||
|
|
||||||
|
public JdbcStockRepository(JdbcClient jdbc) {
|
||||||
|
this.jdbc = jdbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Optional<Stock>> findById(StockId id) {
|
||||||
|
try {
|
||||||
|
var stockOpt = jdbc.sql("SELECT * FROM stocks WHERE id = :id")
|
||||||
|
.param("id", id.value())
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.optional();
|
||||||
|
if (stockOpt.isEmpty()) {
|
||||||
|
return Result.success(Optional.empty());
|
||||||
|
}
|
||||||
|
return Result.success(Optional.of(loadChildren(stockOpt.get(), id.value())));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Optional<Stock>> findByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) {
|
||||||
|
try {
|
||||||
|
var stockOpt = jdbc.sql("SELECT * FROM stocks WHERE article_id = :articleId AND storage_location_id = :storageLocationId")
|
||||||
|
.param("articleId", articleId.value())
|
||||||
|
.param("storageLocationId", storageLocationId.value())
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.optional();
|
||||||
|
if (stockOpt.isEmpty()) {
|
||||||
|
return Result.success(Optional.empty());
|
||||||
|
}
|
||||||
|
var stock = stockOpt.get();
|
||||||
|
return Result.success(Optional.of(loadChildren(stock, stock.id().value())));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByArticleIdAndStorageLocationId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Boolean> existsByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) {
|
||||||
|
try {
|
||||||
|
int count = jdbc.sql("SELECT COUNT(*) FROM stocks WHERE article_id = :articleId AND storage_location_id = :storageLocationId")
|
||||||
|
.param("articleId", articleId.value())
|
||||||
|
.param("storageLocationId", storageLocationId.value())
|
||||||
|
.query(Integer.class)
|
||||||
|
.single();
|
||||||
|
return Result.success(count > 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByArticleIdAndStorageLocationId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<Stock>> findAll() {
|
||||||
|
try {
|
||||||
|
var stocks = jdbc.sql("SELECT * FROM stocks")
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(loadChildrenForAll(stocks));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<Stock>> findAllByStorageLocationId(StorageLocationId storageLocationId) {
|
||||||
|
try {
|
||||||
|
var stocks = jdbc.sql("SELECT * FROM stocks WHERE storage_location_id = :storageLocationId")
|
||||||
|
.param("storageLocationId", storageLocationId.value())
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(loadChildrenForAll(stocks));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByStorageLocationId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<Stock>> findAllByArticleId(ArticleId articleId) {
|
||||||
|
try {
|
||||||
|
var stocks = jdbc.sql("SELECT * FROM stocks WHERE article_id = :articleId")
|
||||||
|
.param("articleId", articleId.value())
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(loadChildrenForAll(stocks));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllByArticleId", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<Stock>> findAllWithExpiryRelevantBatches(LocalDate referenceDate) {
|
||||||
|
try {
|
||||||
|
var stocks = jdbc.sql("""
|
||||||
|
SELECT DISTINCT s.* FROM stocks s
|
||||||
|
JOIN stock_batches b ON b.stock_id = s.id
|
||||||
|
WHERE (b.status IN ('AVAILABLE', 'EXPIRING_SOON') AND b.expiry_date < :today)
|
||||||
|
OR (s.minimum_shelf_life_days IS NOT NULL AND b.status = 'AVAILABLE'
|
||||||
|
AND b.expiry_date >= :today
|
||||||
|
AND b.expiry_date < :today + s.minimum_shelf_life_days * INTERVAL '1 day')
|
||||||
|
""")
|
||||||
|
.param("today", referenceDate)
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(loadChildrenForAll(stocks));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllWithExpiryRelevantBatches", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<Stock>> findAllBelowMinimumLevel() {
|
||||||
|
try {
|
||||||
|
var stocks = jdbc.sql("""
|
||||||
|
SELECT DISTINCT s.* FROM stocks s
|
||||||
|
LEFT JOIN stock_batches b ON b.stock_id = s.id AND b.status IN ('AVAILABLE', 'EXPIRING_SOON')
|
||||||
|
WHERE s.minimum_level_amount IS NOT NULL
|
||||||
|
GROUP BY s.id
|
||||||
|
HAVING COALESCE(SUM(b.quantity_amount), 0) < s.minimum_level_amount
|
||||||
|
""")
|
||||||
|
.query(this::mapStockRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(loadChildrenForAll(stocks));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAllBelowMinimumLevel", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Void> save(Stock stock) {
|
||||||
|
try {
|
||||||
|
int rows = jdbc.sql("""
|
||||||
|
UPDATE stocks
|
||||||
|
SET article_id = :articleId, storage_location_id = :storageLocationId,
|
||||||
|
minimum_level_amount = :minimumLevelAmount, minimum_level_unit = :minimumLevelUnit,
|
||||||
|
minimum_shelf_life_days = :minimumShelfLifeDays
|
||||||
|
WHERE id = :id
|
||||||
|
""")
|
||||||
|
.param("id", stock.id().value())
|
||||||
|
.params(stockParams(stock))
|
||||||
|
.update();
|
||||||
|
|
||||||
|
if (rows == 0) {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO stocks (id, article_id, storage_location_id,
|
||||||
|
minimum_level_amount, minimum_level_unit, minimum_shelf_life_days)
|
||||||
|
VALUES (:id, :articleId, :storageLocationId,
|
||||||
|
:minimumLevelAmount, :minimumLevelUnit, :minimumShelfLifeDays)
|
||||||
|
""")
|
||||||
|
.param("id", stock.id().value())
|
||||||
|
.params(stockParams(stock))
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveChildren(stock);
|
||||||
|
|
||||||
|
return Result.success(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
|
if (e.getMessage() != null && e.getMessage().contains("duplicate key")
|
||||||
|
|| e.getMessage() != null && e.getMessage().contains("unique constraint")) {
|
||||||
|
return Result.failure(new RepositoryError.DuplicateEntry(
|
||||||
|
"Stock already exists for article " + stock.articleId().value()
|
||||||
|
+ " at location " + stock.storageLocationId().value()));
|
||||||
|
}
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Private Helpers ====================
|
||||||
|
|
||||||
|
private java.util.Map<String, Object> stockParams(Stock stock) {
|
||||||
|
var params = new java.util.LinkedHashMap<String, Object>();
|
||||||
|
params.put("articleId", stock.articleId().value());
|
||||||
|
params.put("storageLocationId", stock.storageLocationId().value());
|
||||||
|
params.put("minimumLevelAmount", stock.minimumLevel() != null ? stock.minimumLevel().quantity().amount() : null);
|
||||||
|
params.put("minimumLevelUnit", stock.minimumLevel() != null ? stock.minimumLevel().quantity().uom().name() : null);
|
||||||
|
params.put("minimumShelfLifeDays", stock.minimumShelfLife() != null ? stock.minimumShelfLife().days() : null);
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveChildren(Stock stock) {
|
||||||
|
String stockId = stock.id().value();
|
||||||
|
|
||||||
|
// Delete reservations first (cascades to allocations via FK ON DELETE CASCADE)
|
||||||
|
// Must happen before deleting batches due to FK from allocations -> stock_batches
|
||||||
|
jdbc.sql("DELETE FROM reservations WHERE stock_id = :stockId")
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.update();
|
||||||
|
|
||||||
|
// Delete + re-insert batches
|
||||||
|
jdbc.sql("DELETE FROM stock_batches WHERE stock_id = :stockId")
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.update();
|
||||||
|
|
||||||
|
for (StockBatch batch : stock.batches()) {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO stock_batches
|
||||||
|
(id, stock_id, batch_id, batch_type, quantity_amount, quantity_unit, expiry_date, status, received_at)
|
||||||
|
VALUES (:id, :stockId, :batchId, :batchType, :quantityAmount, :quantityUnit, :expiryDate, :status, :receivedAt)
|
||||||
|
""")
|
||||||
|
.param("id", batch.id().value())
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.param("batchId", batch.batchReference().batchId())
|
||||||
|
.param("batchType", batch.batchReference().batchType().name())
|
||||||
|
.param("quantityAmount", batch.quantity().amount())
|
||||||
|
.param("quantityUnit", batch.quantity().uom().name())
|
||||||
|
.param("expiryDate", batch.expiryDate())
|
||||||
|
.param("status", batch.status().name())
|
||||||
|
.param("receivedAt", batch.receivedAt())
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Reservation reservation : stock.reservations()) {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO reservations
|
||||||
|
(id, stock_id, reference_type, reference_id, quantity_amount, quantity_unit, priority, reserved_at)
|
||||||
|
VALUES (:id, :stockId, :referenceType, :referenceId, :quantityAmount, :quantityUnit, :priority, :reservedAt)
|
||||||
|
""")
|
||||||
|
.param("id", reservation.id().value())
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.param("referenceType", reservation.referenceType().name())
|
||||||
|
.param("referenceId", reservation.referenceId())
|
||||||
|
.param("quantityAmount", reservation.quantity().amount())
|
||||||
|
.param("quantityUnit", reservation.quantity().uom().name())
|
||||||
|
.param("priority", reservation.priority().name())
|
||||||
|
.param("reservedAt", reservation.reservedAt())
|
||||||
|
.update();
|
||||||
|
|
||||||
|
for (StockBatchAllocation allocation : reservation.allocations()) {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO stock_batch_allocations
|
||||||
|
(id, reservation_id, stock_batch_id, allocated_quantity_amount, allocated_quantity_unit)
|
||||||
|
VALUES (:id, :reservationId, :stockBatchId, :allocatedQuantityAmount, :allocatedQuantityUnit)
|
||||||
|
""")
|
||||||
|
.param("id", allocation.id().value())
|
||||||
|
.param("reservationId", reservation.id().value())
|
||||||
|
.param("stockBatchId", allocation.stockBatchId().value())
|
||||||
|
.param("allocatedQuantityAmount", allocation.allocatedQuantity().amount())
|
||||||
|
.param("allocatedQuantityUnit", allocation.allocatedQuantity().uom().name())
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stock loadChildren(Stock stock, String stockId) {
|
||||||
|
var batches = jdbc.sql("SELECT * FROM stock_batches WHERE stock_id = :stockId")
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.query(this::mapBatchRow)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
var reservations = loadReservations(stockId);
|
||||||
|
|
||||||
|
return Stock.reconstitute(
|
||||||
|
stock.id(), stock.articleId(), stock.storageLocationId(),
|
||||||
|
stock.minimumLevel(), stock.minimumShelfLife(),
|
||||||
|
batches, reservations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Stock> loadChildrenForAll(List<Stock> stocks) {
|
||||||
|
return stocks.stream()
|
||||||
|
.map(s -> loadChildren(s, s.id().value()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Reservation> loadReservations(String stockId) {
|
||||||
|
var reservationRows = jdbc.sql("SELECT * FROM reservations WHERE stock_id = :stockId")
|
||||||
|
.param("stockId", stockId)
|
||||||
|
.query(this::mapReservationRow)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
if (reservationRows.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> reservationIds = reservationRows.stream()
|
||||||
|
.map(r -> r.id().value())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
var allAllocations = jdbc.sql("SELECT * FROM stock_batch_allocations WHERE reservation_id IN (:reservationIds)")
|
||||||
|
.param("reservationIds", reservationIds)
|
||||||
|
.query(this::mapAllocationRow)
|
||||||
|
.list();
|
||||||
|
|
||||||
|
// Group allocations by reservation_id
|
||||||
|
var allocationsByReservation = new java.util.HashMap<String, List<StockBatchAllocation>>();
|
||||||
|
for (var entry : allAllocations) {
|
||||||
|
allocationsByReservation.computeIfAbsent(entry.reservationId(), k -> new ArrayList<>())
|
||||||
|
.add(entry.allocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservationRows.stream()
|
||||||
|
.map(r -> new Reservation(
|
||||||
|
r.id(), r.referenceType(), r.referenceId(),
|
||||||
|
r.quantity(), r.priority(), r.reservedAt(),
|
||||||
|
allocationsByReservation.getOrDefault(r.id().value(), List.of())
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Row Mappers ====================
|
||||||
|
|
||||||
|
private Stock mapStockRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
MinimumLevel minimumLevel = null;
|
||||||
|
var levelAmount = rs.getBigDecimal("minimum_level_amount");
|
||||||
|
String levelUnit = rs.getString("minimum_level_unit");
|
||||||
|
if (levelAmount != null && levelUnit != null) {
|
||||||
|
minimumLevel = new MinimumLevel(Quantity.reconstitute(levelAmount, UnitOfMeasure.valueOf(levelUnit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
MinimumShelfLife minimumShelfLife = null;
|
||||||
|
int shelfLifeDays = rs.getInt("minimum_shelf_life_days");
|
||||||
|
if (!rs.wasNull()) {
|
||||||
|
minimumShelfLife = new MinimumShelfLife(shelfLifeDays);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stock.reconstitute(
|
||||||
|
StockId.of(rs.getString("id")),
|
||||||
|
ArticleId.of(rs.getString("article_id")),
|
||||||
|
StorageLocationId.of(rs.getString("storage_location_id")),
|
||||||
|
minimumLevel, minimumShelfLife,
|
||||||
|
List.of(), List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StockBatch mapBatchRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
return StockBatch.reconstitute(
|
||||||
|
StockBatchId.of(rs.getString("id")),
|
||||||
|
new BatchReference(rs.getString("batch_id"), BatchType.valueOf(rs.getString("batch_type"))),
|
||||||
|
Quantity.reconstitute(
|
||||||
|
rs.getBigDecimal("quantity_amount"),
|
||||||
|
UnitOfMeasure.valueOf(rs.getString("quantity_unit"))
|
||||||
|
),
|
||||||
|
rs.getObject("expiry_date", LocalDate.class),
|
||||||
|
StockBatchStatus.valueOf(rs.getString("status")),
|
||||||
|
rs.getObject("received_at", Instant.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReservationRow mapReservationRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
return new ReservationRow(
|
||||||
|
ReservationId.of(rs.getString("id")),
|
||||||
|
ReferenceType.valueOf(rs.getString("reference_type")),
|
||||||
|
rs.getString("reference_id"),
|
||||||
|
Quantity.reconstitute(
|
||||||
|
rs.getBigDecimal("quantity_amount"),
|
||||||
|
UnitOfMeasure.valueOf(rs.getString("quantity_unit"))
|
||||||
|
),
|
||||||
|
ReservationPriority.valueOf(rs.getString("priority")),
|
||||||
|
rs.getObject("reserved_at", Instant.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllocationRow mapAllocationRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
return new AllocationRow(
|
||||||
|
rs.getString("reservation_id"),
|
||||||
|
new StockBatchAllocation(
|
||||||
|
AllocationId.of(rs.getString("id")),
|
||||||
|
StockBatchId.of(rs.getString("stock_batch_id")),
|
||||||
|
Quantity.reconstitute(
|
||||||
|
rs.getBigDecimal("allocated_quantity_amount"),
|
||||||
|
UnitOfMeasure.valueOf(rs.getString("allocated_quantity_unit"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ReservationRow(
|
||||||
|
ReservationId id, ReferenceType referenceType, String referenceId,
|
||||||
|
Quantity quantity, ReservationPriority priority, Instant reservedAt
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private record AllocationRow(String reservationId, StockBatchAllocation allocation) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
package de.effigenix.infrastructure.inventory.persistence.repository;
|
||||||
|
|
||||||
|
import de.effigenix.domain.inventory.*;
|
||||||
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
|
import de.effigenix.shared.common.Result;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@Profile("!no-db")
|
||||||
|
public class JdbcStorageLocationRepository implements StorageLocationRepository {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JdbcStorageLocationRepository.class);
|
||||||
|
|
||||||
|
private final JdbcClient jdbc;
|
||||||
|
|
||||||
|
public JdbcStorageLocationRepository(JdbcClient jdbc) {
|
||||||
|
this.jdbc = jdbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Optional<StorageLocation>> findById(StorageLocationId id) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM storage_locations WHERE id = :id")
|
||||||
|
.param("id", id.value())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.optional();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findById", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StorageLocation>> findAll() {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM storage_locations ORDER BY name")
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findAll", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StorageLocation>> findByStorageType(StorageType storageType) {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM storage_locations WHERE storage_type = :storageType ORDER BY name")
|
||||||
|
.param("storageType", storageType.name())
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findByStorageType", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, List<StorageLocation>> findActive() {
|
||||||
|
try {
|
||||||
|
var result = jdbc.sql("SELECT * FROM storage_locations WHERE active = true ORDER BY name")
|
||||||
|
.query(this::mapRow)
|
||||||
|
.list();
|
||||||
|
return Result.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in findActive", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Boolean> existsByName(StorageLocationName name) {
|
||||||
|
try {
|
||||||
|
int count = jdbc.sql("SELECT COUNT(*) FROM storage_locations WHERE name = :name")
|
||||||
|
.param("name", name.value())
|
||||||
|
.query(Integer.class)
|
||||||
|
.single();
|
||||||
|
return Result.success(count > 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByName", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Boolean> existsByNameAndIdNot(StorageLocationName name, StorageLocationId id) {
|
||||||
|
try {
|
||||||
|
int count = jdbc.sql("SELECT COUNT(*) FROM storage_locations WHERE name = :name AND id != :id")
|
||||||
|
.param("name", name.value())
|
||||||
|
.param("id", id.value())
|
||||||
|
.query(Integer.class)
|
||||||
|
.single();
|
||||||
|
return Result.success(count > 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in existsByNameAndIdNot", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<RepositoryError, Void> save(StorageLocation location) {
|
||||||
|
try {
|
||||||
|
int rows = jdbc.sql("""
|
||||||
|
UPDATE storage_locations
|
||||||
|
SET name = :name, storage_type = :storageType,
|
||||||
|
min_temperature = :minTemperature, max_temperature = :maxTemperature,
|
||||||
|
active = :active
|
||||||
|
WHERE id = :id
|
||||||
|
""")
|
||||||
|
.param("id", location.id().value())
|
||||||
|
.params(locationParams(location))
|
||||||
|
.update();
|
||||||
|
|
||||||
|
if (rows == 0) {
|
||||||
|
jdbc.sql("""
|
||||||
|
INSERT INTO storage_locations (id, name, storage_type, min_temperature, max_temperature, active)
|
||||||
|
VALUES (:id, :name, :storageType, :minTemperature, :maxTemperature, :active)
|
||||||
|
""")
|
||||||
|
.param("id", location.id().value())
|
||||||
|
.params(locationParams(location))
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.trace("Database error in save", e);
|
||||||
|
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.util.Map<String, Object> locationParams(StorageLocation location) {
|
||||||
|
var params = new java.util.LinkedHashMap<String, Object>();
|
||||||
|
params.put("name", location.name().value());
|
||||||
|
params.put("storageType", location.storageType().name());
|
||||||
|
params.put("minTemperature", location.temperatureRange() != null ? location.temperatureRange().minTemperature() : null);
|
||||||
|
params.put("maxTemperature", location.temperatureRange() != null ? location.temperatureRange().maxTemperature() : null);
|
||||||
|
params.put("active", location.active());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StorageLocation mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||||
|
TemperatureRange temperatureRange = null;
|
||||||
|
BigDecimal minTemp = rs.getBigDecimal("min_temperature");
|
||||||
|
BigDecimal maxTemp = rs.getBigDecimal("max_temperature");
|
||||||
|
if (minTemp != null && maxTemp != null) {
|
||||||
|
temperatureRange = new TemperatureRange(minTemp, maxTemp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StorageLocation.reconstitute(
|
||||||
|
StorageLocationId.of(rs.getString("id")),
|
||||||
|
new StorageLocationName(rs.getString("name")),
|
||||||
|
StorageType.valueOf(rs.getString("storage_type")),
|
||||||
|
temperatureRange,
|
||||||
|
rs.getBoolean("active")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.mapper.StockMovementMapper;
|
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
|
||||||
import de.effigenix.shared.common.Result;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
@Profile("!no-db")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class JpaStockMovementRepository implements StockMovementRepository {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JpaStockMovementRepository.class);
|
|
||||||
|
|
||||||
private final StockMovementJpaRepository jpaRepository;
|
|
||||||
private final StockMovementMapper mapper;
|
|
||||||
|
|
||||||
public JpaStockMovementRepository(StockMovementJpaRepository jpaRepository, StockMovementMapper mapper) {
|
|
||||||
this.jpaRepository = jpaRepository;
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Optional<StockMovement>> findById(StockMovementId id) {
|
|
||||||
try {
|
|
||||||
Optional<StockMovement> result = jpaRepository.findById(id.value())
|
|
||||||
.map(mapper::toDomain);
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findById", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAll() {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAll().stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAll", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByStockId(StockId stockId) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByStockId(stockId.value()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByStockId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByArticleId(ArticleId articleId) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByArticleId(articleId.value()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByArticleId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByMovementType(MovementType movementType) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByMovementType(movementType.name()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByMovementType", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByBatchReference(String batchReference) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByBatchId(batchReference).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByBatchReference", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtBetween(Instant from, Instant to) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByPerformedAtBetween(from, to).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByPerformedAtBetween", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtAfter(Instant from) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByPerformedAtGreaterThanEqual(from).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByPerformedAtAfter", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StockMovement>> findAllByPerformedAtBefore(Instant to) {
|
|
||||||
try {
|
|
||||||
List<StockMovement> result = jpaRepository.findAllByPerformedAtLessThanEqual(to).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in findAllByPerformedAtBefore", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public Result<RepositoryError, Void> save(StockMovement stockMovement) {
|
|
||||||
try {
|
|
||||||
jpaRepository.save(mapper.toEntity(stockMovement));
|
|
||||||
return Result.success(null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Database error in save", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.mapper.StockMapper;
|
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
|
||||||
import de.effigenix.shared.common.Result;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
@Profile("!no-db")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class JpaStockRepository implements StockRepository {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JpaStockRepository.class);
|
|
||||||
|
|
||||||
private final StockJpaRepository jpaRepository;
|
|
||||||
private final StockMapper mapper;
|
|
||||||
|
|
||||||
public JpaStockRepository(StockJpaRepository jpaRepository, StockMapper mapper) {
|
|
||||||
this.jpaRepository = jpaRepository;
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Optional<Stock>> findById(StockId id) {
|
|
||||||
try {
|
|
||||||
Optional<Stock> result = jpaRepository.findById(id.value())
|
|
||||||
.map(mapper::toDomain);
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findById", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Optional<Stock>> findByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) {
|
|
||||||
try {
|
|
||||||
Optional<Stock> result = jpaRepository.findByArticleIdAndStorageLocationId(articleId.value(), storageLocationId.value())
|
|
||||||
.map(mapper::toDomain);
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findByArticleIdAndStorageLocationId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Boolean> existsByArticleIdAndStorageLocationId(ArticleId articleId, StorageLocationId storageLocationId) {
|
|
||||||
try {
|
|
||||||
return Result.success(jpaRepository.existsByArticleIdAndStorageLocationId(articleId.value(), storageLocationId.value()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in existsByArticleIdAndStorageLocationId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<Stock>> findAll() {
|
|
||||||
try {
|
|
||||||
List<Stock> result = jpaRepository.findAll().stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAll", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<Stock>> findAllByStorageLocationId(StorageLocationId storageLocationId) {
|
|
||||||
try {
|
|
||||||
List<Stock> result = jpaRepository.findAllByStorageLocationId(storageLocationId.value()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAllByStorageLocationId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<Stock>> findAllByArticleId(ArticleId articleId) {
|
|
||||||
try {
|
|
||||||
List<Stock> result = jpaRepository.findAllByArticleId(articleId.value()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAllByArticleId", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<Stock>> findAllWithExpiryRelevantBatches(LocalDate referenceDate) {
|
|
||||||
try {
|
|
||||||
List<Stock> result = jpaRepository.findAllWithExpiryRelevantBatches(referenceDate).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAllWithExpiryRelevantBatches", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<Stock>> findAllBelowMinimumLevel() {
|
|
||||||
try {
|
|
||||||
List<Stock> result = jpaRepository.findAllBelowMinimumLevel().stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.toList();
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAllBelowMinimumLevel", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public Result<RepositoryError, Void> save(Stock stock) {
|
|
||||||
try {
|
|
||||||
jpaRepository.save(mapper.toEntity(stock));
|
|
||||||
return Result.success(null);
|
|
||||||
} catch (DataIntegrityViolationException e) {
|
|
||||||
logger.trace("Duplicate entry in save", e);
|
|
||||||
return Result.failure(new RepositoryError.DuplicateEntry(
|
|
||||||
"Stock already exists for article " + stock.articleId().value()
|
|
||||||
+ " at location " + stock.storageLocationId().value()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in save", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.domain.inventory.*;
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.mapper.StorageLocationMapper;
|
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
|
||||||
import de.effigenix.shared.common.Result;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
@Profile("!no-db")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class JpaStorageLocationRepository implements StorageLocationRepository {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(JpaStorageLocationRepository.class);
|
|
||||||
|
|
||||||
private final StorageLocationJpaRepository jpaRepository;
|
|
||||||
private final StorageLocationMapper mapper;
|
|
||||||
|
|
||||||
public JpaStorageLocationRepository(StorageLocationJpaRepository jpaRepository, StorageLocationMapper mapper) {
|
|
||||||
this.jpaRepository = jpaRepository;
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Optional<StorageLocation>> findById(StorageLocationId id) {
|
|
||||||
try {
|
|
||||||
Optional<StorageLocation> result = jpaRepository.findById(id.value())
|
|
||||||
.map(mapper::toDomain);
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findById", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StorageLocation>> findAll() {
|
|
||||||
try {
|
|
||||||
List<StorageLocation> result = jpaRepository.findAll().stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findAll", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StorageLocation>> findByStorageType(StorageType storageType) {
|
|
||||||
try {
|
|
||||||
List<StorageLocation> result = jpaRepository.findByStorageType(storageType.name()).stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findByStorageType", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, List<StorageLocation>> findActive() {
|
|
||||||
try {
|
|
||||||
List<StorageLocation> result = jpaRepository.findByActiveTrue().stream()
|
|
||||||
.map(mapper::toDomain)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return Result.success(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in findActive", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<RepositoryError, Boolean> existsByName(StorageLocationName name) {
|
|
||||||
try {
|
|
||||||
return Result.success(jpaRepository.existsByName(name.value()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in existsByName", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
try {
|
|
||||||
jpaRepository.save(mapper.toEntity(location));
|
|
||||||
return Result.success(null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.trace("Database error in save", e);
|
|
||||||
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockEntity;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface StockJpaRepository extends JpaRepository<StockEntity, String> {
|
|
||||||
|
|
||||||
Optional<StockEntity> findByArticleIdAndStorageLocationId(String articleId, String storageLocationId);
|
|
||||||
|
|
||||||
boolean existsByArticleIdAndStorageLocationId(String articleId, String storageLocationId);
|
|
||||||
|
|
||||||
List<StockEntity> findAllByStorageLocationId(String storageLocationId);
|
|
||||||
|
|
||||||
List<StockEntity> findAllByArticleId(String articleId);
|
|
||||||
|
|
||||||
@Query(value = """
|
|
||||||
SELECT DISTINCT s.* FROM stocks s \
|
|
||||||
JOIN stock_batches b ON b.stock_id = s.id \
|
|
||||||
WHERE (b.status IN ('AVAILABLE', 'EXPIRING_SOON') AND b.expiry_date < :today) \
|
|
||||||
OR (s.minimum_shelf_life_days IS NOT NULL AND b.status = 'AVAILABLE' \
|
|
||||||
AND b.expiry_date >= :today \
|
|
||||||
AND b.expiry_date < :today + s.minimum_shelf_life_days * INTERVAL '1 day')""",
|
|
||||||
nativeQuery = true)
|
|
||||||
List<StockEntity> findAllWithExpiryRelevantBatches(@Param("today") LocalDate today);
|
|
||||||
|
|
||||||
@Query(value = """
|
|
||||||
SELECT DISTINCT s.* FROM stocks s \
|
|
||||||
LEFT JOIN stock_batches b ON b.stock_id = s.id AND b.status IN ('AVAILABLE', 'EXPIRING_SOON') \
|
|
||||||
WHERE s.minimum_level_amount IS NOT NULL \
|
|
||||||
GROUP BY s.id \
|
|
||||||
HAVING COALESCE(SUM(b.quantity_amount), 0) < s.minimum_level_amount""",
|
|
||||||
nativeQuery = true)
|
|
||||||
List<StockEntity> findAllBelowMinimumLevel();
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StockMovementEntity;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface StockMovementJpaRepository extends JpaRepository<StockMovementEntity, String> {
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByStockId(String stockId);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByArticleId(String articleId);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByMovementType(String movementType);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByBatchId(String batchId);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByPerformedAtBetween(Instant from, Instant to);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByPerformedAtGreaterThanEqual(Instant from);
|
|
||||||
|
|
||||||
List<StockMovementEntity> findAllByPerformedAtLessThanEqual(Instant to);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package de.effigenix.infrastructure.inventory.persistence.repository;
|
|
||||||
|
|
||||||
import de.effigenix.infrastructure.inventory.persistence.entity.StorageLocationEntity;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface StorageLocationJpaRepository extends JpaRepository<StorageLocationEntity, String> {
|
|
||||||
|
|
||||||
List<StorageLocationEntity> findByStorageType(String storageType);
|
|
||||||
|
|
||||||
List<StorageLocationEntity> findByActiveTrue();
|
|
||||||
|
|
||||||
boolean existsByName(String name);
|
|
||||||
|
|
||||||
boolean existsByNameAndIdNot(String name, String id);
|
|
||||||
}
|
|
||||||
|
|
@ -6,6 +6,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -28,6 +29,7 @@ import static org.mockito.Mockito.*;
|
||||||
class AddStockBatchTest {
|
class AddStockBatchTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private AddStockBatch addStockBatch;
|
private AddStockBatch addStockBatch;
|
||||||
private AddStockBatchCommand validCommand;
|
private AddStockBatchCommand validCommand;
|
||||||
|
|
@ -35,7 +37,8 @@ class AddStockBatchTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
addStockBatch = new AddStockBatch(stockRepository);
|
addStockBatch = new AddStockBatch(stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
existingStock = Stock.reconstitute(
|
existingStock = Stock.reconstitute(
|
||||||
StockId.of("stock-1"),
|
StockId.of("stock-1"),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
@ -32,6 +33,7 @@ class BlockStockBatchTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
@Mock private AuditLogger auditLogger;
|
@Mock private AuditLogger auditLogger;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private BlockStockBatch blockStockBatch;
|
private BlockStockBatch blockStockBatch;
|
||||||
private StockBatchId batchId;
|
private StockBatchId batchId;
|
||||||
|
|
@ -41,7 +43,8 @@ class BlockStockBatchTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
blockStockBatch = new BlockStockBatch(stockRepository, auditLogger);
|
blockStockBatch = new BlockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
batchId = StockBatchId.of("batch-1");
|
batchId = StockBatchId.of("batch-1");
|
||||||
var batch = StockBatch.reconstitute(
|
var batch = StockBatch.reconstitute(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.domain.masterdata.ArticleId;
|
import de.effigenix.domain.masterdata.ArticleId;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
@ -17,9 +18,8 @@ import java.util.Optional;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@DisplayName("DeactivateStorageLocation Use Case")
|
@DisplayName("DeactivateStorageLocation Use Case")
|
||||||
|
|
@ -27,13 +27,15 @@ class DeactivateStorageLocationTest {
|
||||||
|
|
||||||
@Mock private StorageLocationRepository storageLocationRepository;
|
@Mock private StorageLocationRepository storageLocationRepository;
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private DeactivateStorageLocation deactivateStorageLocation;
|
private DeactivateStorageLocation deactivateStorageLocation;
|
||||||
private ActorId performedBy;
|
private ActorId performedBy;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
deactivateStorageLocation = new DeactivateStorageLocation(storageLocationRepository, stockRepository);
|
deactivateStorageLocation = new DeactivateStorageLocation(storageLocationRepository, stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
performedBy = ActorId.of("admin-user");
|
performedBy = ActorId.of("admin-user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import de.effigenix.application.inventory.command.RecordStockMovementCommand;
|
||||||
import de.effigenix.domain.inventory.*;
|
import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import de.effigenix.shared.security.AuthorizationPort;
|
import de.effigenix.shared.security.AuthorizationPort;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
@ -19,6 +20,7 @@ class RecordStockMovementTest {
|
||||||
|
|
||||||
private StockMovementRepository repository;
|
private StockMovementRepository repository;
|
||||||
private AuthorizationPort authPort;
|
private AuthorizationPort authPort;
|
||||||
|
private UnitOfWork unitOfWork;
|
||||||
private RecordStockMovement useCase;
|
private RecordStockMovement useCase;
|
||||||
private final ActorId actor = ActorId.of("user-1");
|
private final ActorId actor = ActorId.of("user-1");
|
||||||
|
|
||||||
|
|
@ -26,7 +28,9 @@ class RecordStockMovementTest {
|
||||||
void setUp() {
|
void setUp() {
|
||||||
repository = mock(StockMovementRepository.class);
|
repository = mock(StockMovementRepository.class);
|
||||||
authPort = mock(AuthorizationPort.class);
|
authPort = mock(AuthorizationPort.class);
|
||||||
useCase = new RecordStockMovement(repository, authPort);
|
unitOfWork = mock(UnitOfWork.class);
|
||||||
|
useCase = new RecordStockMovement(repository, authPort, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
when(authPort.can(any(ActorId.class), any())).thenReturn(true);
|
when(authPort.can(any(ActorId.class), any())).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -29,6 +30,7 @@ import static org.mockito.Mockito.*;
|
||||||
class ReleaseReservationTest {
|
class ReleaseReservationTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private ReleaseReservation releaseReservation;
|
private ReleaseReservation releaseReservation;
|
||||||
private Stock existingStock;
|
private Stock existingStock;
|
||||||
|
|
@ -36,7 +38,8 @@ class ReleaseReservationTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
releaseReservation = new ReleaseReservation(stockRepository);
|
releaseReservation = new ReleaseReservation(stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
var batch = StockBatch.reconstitute(
|
var batch = StockBatch.reconstitute(
|
||||||
StockBatchId.generate(),
|
StockBatchId.generate(),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -29,6 +30,7 @@ import static org.mockito.Mockito.*;
|
||||||
class RemoveStockBatchTest {
|
class RemoveStockBatchTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private RemoveStockBatch removeStockBatch;
|
private RemoveStockBatch removeStockBatch;
|
||||||
private StockBatchId batchId;
|
private StockBatchId batchId;
|
||||||
|
|
@ -37,7 +39,8 @@ class RemoveStockBatchTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
removeStockBatch = new RemoveStockBatch(stockRepository);
|
removeStockBatch = new RemoveStockBatch(stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
batchId = StockBatchId.of("batch-1");
|
batchId = StockBatchId.of("batch-1");
|
||||||
var batch = StockBatch.reconstitute(
|
var batch = StockBatch.reconstitute(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -29,6 +30,7 @@ import static org.mockito.Mockito.*;
|
||||||
class ReserveStockTest {
|
class ReserveStockTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private ReserveStock reserveStock;
|
private ReserveStock reserveStock;
|
||||||
private ReserveStockCommand validCommand;
|
private ReserveStockCommand validCommand;
|
||||||
|
|
@ -36,7 +38,8 @@ class ReserveStockTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
reserveStock = new ReserveStock(stockRepository);
|
reserveStock = new ReserveStock(stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
var batch = StockBatch.reconstitute(
|
var batch = StockBatch.reconstitute(
|
||||||
StockBatchId.generate(),
|
StockBatchId.generate(),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import de.effigenix.shared.common.Quantity;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import de.effigenix.shared.security.ActorId;
|
import de.effigenix.shared.security.ActorId;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
|
@ -32,6 +33,7 @@ class UnblockStockBatchTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
@Mock private AuditLogger auditLogger;
|
@Mock private AuditLogger auditLogger;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private UnblockStockBatch unblockStockBatch;
|
private UnblockStockBatch unblockStockBatch;
|
||||||
private StockBatchId batchId;
|
private StockBatchId batchId;
|
||||||
|
|
@ -41,7 +43,8 @@ class UnblockStockBatchTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
unblockStockBatch = new UnblockStockBatch(stockRepository, auditLogger);
|
unblockStockBatch = new UnblockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
batchId = StockBatchId.of("batch-1");
|
batchId = StockBatchId.of("batch-1");
|
||||||
var batch = StockBatch.reconstitute(
|
var batch = StockBatch.reconstitute(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import de.effigenix.domain.inventory.*;
|
||||||
import de.effigenix.shared.common.RepositoryError;
|
import de.effigenix.shared.common.RepositoryError;
|
||||||
import de.effigenix.shared.common.Result;
|
import de.effigenix.shared.common.Result;
|
||||||
import de.effigenix.shared.common.UnitOfMeasure;
|
import de.effigenix.shared.common.UnitOfMeasure;
|
||||||
|
import de.effigenix.shared.persistence.UnitOfWork;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -18,19 +19,22 @@ import java.util.Optional;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@DisplayName("UpdateStock Use Case")
|
@DisplayName("UpdateStock Use Case")
|
||||||
class UpdateStockTest {
|
class UpdateStockTest {
|
||||||
|
|
||||||
@Mock private StockRepository stockRepository;
|
@Mock private StockRepository stockRepository;
|
||||||
|
@Mock private UnitOfWork unitOfWork;
|
||||||
|
|
||||||
private UpdateStock updateStock;
|
private UpdateStock updateStock;
|
||||||
private Stock existingStock;
|
private Stock existingStock;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
updateStock = new UpdateStock(stockRepository);
|
updateStock = new UpdateStock(stockRepository, unitOfWork);
|
||||||
|
lenient().when(unitOfWork.executeAtomically(any())).thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
|
||||||
|
|
||||||
existingStock = Stock.reconstitute(
|
existingStock = Stock.reconstitute(
|
||||||
StockId.of("stock-1"),
|
StockId.of("stock-1"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue