mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 15:49: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.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class ActivateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
public ActivateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||
|
|
@ -36,12 +37,13 @@ public class ActivateStorageLocation {
|
|||
}
|
||||
|
||||
// 3. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
|||
import de.effigenix.application.inventory.command.AddStockBatchCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
@Transactional
|
||||
public class AddStockBatch {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public AddStockBatch(StockRepository stockRepository) {
|
||||
public AddStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, StockBatch> execute(AddStockBatchCommand cmd) {
|
||||
|
|
@ -42,12 +43,13 @@ public class AddStockBatch {
|
|||
}
|
||||
|
||||
// 3. Stock speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(batch);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(batch);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,19 @@ import de.effigenix.application.usermanagement.AuditEvent;
|
|||
import de.effigenix.application.usermanagement.AuditLogger;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class BlockStockBatch {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
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.auditLogger = auditLogger;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Void> execute(BlockStockBatchCommand cmd, ActorId performedBy) {
|
||||
|
|
@ -40,13 +41,14 @@ public class BlockStockBatch {
|
|||
}
|
||||
|
||||
// 3. Stock speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ 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);
|
||||
return Result.success(null);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ 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);
|
||||
return Result.success(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import de.effigenix.application.inventory.command.CreateStockCommand;
|
|||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
@Transactional
|
||||
public class CreateStock {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public CreateStock(StockRepository stockRepository) {
|
||||
public CreateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Stock> execute(CreateStockCommand cmd) {
|
||||
|
|
@ -43,17 +44,18 @@ public class CreateStock {
|
|||
}
|
||||
|
||||
// 4. Speichern (Race-Condition-Schutz: DuplicateEntry → DuplicateStock)
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) -> {
|
||||
if (err instanceof RepositoryError.DuplicateEntry) {
|
||||
return Result.failure(new StockError.DuplicateStock(
|
||||
cmd.articleId(), cmd.storageLocationId()));
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) -> {
|
||||
if (err instanceof RepositoryError.DuplicateEntry) {
|
||||
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.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class CreateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public CreateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
public CreateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(CreateStorageLocationCommand cmd, ActorId performedBy) {
|
||||
|
|
@ -40,12 +41,13 @@ public class CreateStorageLocation {
|
|||
}
|
||||
|
||||
// 4. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ package de.effigenix.application.inventory;
|
|||
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class DeactivateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
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.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(String storageLocationId, ActorId performedBy) {
|
||||
|
|
@ -49,12 +50,13 @@ public class DeactivateStorageLocation {
|
|||
}
|
||||
|
||||
// 4. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import de.effigenix.domain.inventory.StockError;
|
|||
import de.effigenix.domain.inventory.StockId;
|
||||
import de.effigenix.domain.inventory.StockRepository;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetStock {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import de.effigenix.domain.inventory.StockMovementRepository;
|
|||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetStockMovement {
|
||||
|
||||
private final StockMovementRepository stockMovementRepository;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import de.effigenix.domain.inventory.StorageLocationRepository;
|
|||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ import de.effigenix.shared.common.RepositoryError;
|
|||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class ListStockMovements {
|
||||
|
||||
private final StockMovementRepository stockMovementRepository;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ import de.effigenix.domain.inventory.StorageLocationId;
|
|||
import de.effigenix.domain.masterdata.ArticleId;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class ListStocks {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import de.effigenix.domain.inventory.StockRepository;
|
|||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ public class ListStocksBelowMinimum {
|
|||
this.authPort = authPort;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Result<StockError, List<Stock>> execute(ActorId performedBy) {
|
||||
if (!authPort.can(performedBy, InventoryAction.STOCK_READ)) {
|
||||
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.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class ListStorageLocations {
|
||||
|
||||
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.StockMovementRepository;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class RecordStockMovement {
|
||||
|
||||
private final StockMovementRepository stockMovementRepository;
|
||||
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.authPort = authPort;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockMovementError, StockMovement> execute(RecordStockMovementCommand cmd, ActorId performedBy) {
|
||||
|
|
@ -42,12 +43,13 @@ public class RecordStockMovement {
|
|||
case Result.Success(var val) -> movement = val;
|
||||
}
|
||||
|
||||
switch (stockMovementRepository.save(movement)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockMovementError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(movement);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockMovementRepository.save(movement)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockMovementError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(movement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
|||
import de.effigenix.application.inventory.command.ReleaseReservationCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
@Transactional
|
||||
public class ReleaseReservation {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public ReleaseReservation(StockRepository stockRepository) {
|
||||
public ReleaseReservation(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Void> execute(ReleaseReservationCommand cmd) {
|
||||
|
|
@ -35,12 +36,13 @@ public class ReleaseReservation {
|
|||
}
|
||||
|
||||
// 3. Speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(null);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@ import de.effigenix.domain.inventory.*;
|
|||
import de.effigenix.shared.common.Quantity;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.common.UnitOfMeasure;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Transactional
|
||||
public class RemoveStockBatch {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public RemoveStockBatch(StockRepository stockRepository) {
|
||||
public RemoveStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Void> execute(RemoveStockBatchCommand cmd) {
|
||||
|
|
@ -59,12 +60,13 @@ public class RemoveStockBatch {
|
|||
}
|
||||
|
||||
// 4. Stock speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(null);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
|||
import de.effigenix.application.inventory.command.ReserveStockCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
@Transactional
|
||||
public class ReserveStock {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public ReserveStock(StockRepository stockRepository) {
|
||||
public ReserveStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Reservation> execute(ReserveStockCommand cmd) {
|
||||
|
|
@ -47,12 +48,13 @@ public class ReserveStock {
|
|||
}
|
||||
|
||||
// 4. Speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(reservation);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(reservation);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,21 @@ import de.effigenix.application.usermanagement.AuditEvent;
|
|||
import de.effigenix.application.usermanagement.AuditLogger;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Transactional
|
||||
public class UnblockStockBatch {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
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.auditLogger = auditLogger;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Void> execute(UnblockStockBatchCommand cmd, ActorId performedBy) {
|
||||
|
|
@ -42,13 +43,14 @@ public class UnblockStockBatch {
|
|||
}
|
||||
|
||||
// 3. Stock speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
auditLogger.log(AuditEvent.STOCK_BATCH_UNBLOCKED, cmd.batchId(), performedBy);
|
||||
return Result.success(null);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
auditLogger.log(AuditEvent.STOCK_BATCH_UNBLOCKED, cmd.batchId(), performedBy);
|
||||
return Result.success(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package de.effigenix.application.inventory;
|
|||
import de.effigenix.application.inventory.command.UpdateStockCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
|
||||
@Transactional
|
||||
public class UpdateStock {
|
||||
|
||||
private final StockRepository stockRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public UpdateStock(StockRepository stockRepository) {
|
||||
public UpdateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
this.stockRepository = stockRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StockError, Stock> execute(UpdateStockCommand cmd) {
|
||||
|
|
@ -46,12 +47,13 @@ public class UpdateStock {
|
|||
}
|
||||
|
||||
// 3. Speichern
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(stock);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (stockRepository.save(stock)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StockError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(stock);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@ package de.effigenix.application.inventory;
|
|||
import de.effigenix.application.inventory.command.UpdateStorageLocationCommand;
|
||||
import de.effigenix.domain.inventory.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Transactional
|
||||
public class UpdateStorageLocation {
|
||||
|
||||
private final StorageLocationRepository storageLocationRepository;
|
||||
private final UnitOfWork unitOfWork;
|
||||
|
||||
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
public UpdateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
this.storageLocationRepository = storageLocationRepository;
|
||||
this.unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public Result<StorageLocationError, StorageLocation> execute(UpdateStorageLocationCommand cmd, ActorId performedBy) {
|
||||
|
|
@ -51,12 +52,13 @@ public class UpdateStorageLocation {
|
|||
}
|
||||
|
||||
// 4. Speichern
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
|
||||
return Result.success(location);
|
||||
return unitOfWork.executeAtomically(() -> {
|
||||
switch (storageLocationRepository.save(location)) {
|
||||
case Result.Failure(var err) ->
|
||||
{ return Result.failure(new StorageLocationError.RepositoryFailure(err.message())); }
|
||||
case Result.Success(var ignored) -> { }
|
||||
}
|
||||
return Result.success(location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import de.effigenix.application.usermanagement.AuditLogger;
|
|||
import de.effigenix.domain.inventory.StockMovementRepository;
|
||||
import de.effigenix.domain.inventory.StockRepository;
|
||||
import de.effigenix.domain.inventory.StorageLocationRepository;
|
||||
import de.effigenix.shared.persistence.UnitOfWork;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
@ -35,23 +36,23 @@ public class InventoryUseCaseConfiguration {
|
|||
// ==================== StorageLocation Use Cases ====================
|
||||
|
||||
@Bean
|
||||
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new CreateStorageLocation(storageLocationRepository);
|
||||
public CreateStorageLocation createStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
return new CreateStorageLocation(storageLocationRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new UpdateStorageLocation(storageLocationRepository);
|
||||
public UpdateStorageLocation updateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
return new UpdateStorageLocation(storageLocationRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository) {
|
||||
return new DeactivateStorageLocation(storageLocationRepository, stockRepository);
|
||||
public DeactivateStorageLocation deactivateStorageLocation(StorageLocationRepository storageLocationRepository, StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new DeactivateStorageLocation(storageLocationRepository, stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository) {
|
||||
return new ActivateStorageLocation(storageLocationRepository);
|
||||
public ActivateStorageLocation activateStorageLocation(StorageLocationRepository storageLocationRepository, UnitOfWork unitOfWork) {
|
||||
return new ActivateStorageLocation(storageLocationRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -67,13 +68,13 @@ public class InventoryUseCaseConfiguration {
|
|||
// ==================== Stock Use Cases ====================
|
||||
|
||||
@Bean
|
||||
public CreateStock createStock(StockRepository stockRepository) {
|
||||
return new CreateStock(stockRepository);
|
||||
public CreateStock createStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new CreateStock(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UpdateStock updateStock(StockRepository stockRepository) {
|
||||
return new UpdateStock(stockRepository);
|
||||
public UpdateStock updateStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new UpdateStock(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -87,33 +88,33 @@ public class InventoryUseCaseConfiguration {
|
|||
}
|
||||
|
||||
@Bean
|
||||
public AddStockBatch addStockBatch(StockRepository stockRepository) {
|
||||
return new AddStockBatch(stockRepository);
|
||||
public AddStockBatch addStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new AddStockBatch(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RemoveStockBatch removeStockBatch(StockRepository stockRepository) {
|
||||
return new RemoveStockBatch(stockRepository);
|
||||
public RemoveStockBatch removeStockBatch(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new RemoveStockBatch(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BlockStockBatch blockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
||||
return new BlockStockBatch(stockRepository, auditLogger);
|
||||
public BlockStockBatch blockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||
return new BlockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UnblockStockBatch unblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger) {
|
||||
return new UnblockStockBatch(stockRepository, auditLogger);
|
||||
public UnblockStockBatch unblockStockBatch(StockRepository stockRepository, AuditLogger auditLogger, UnitOfWork unitOfWork) {
|
||||
return new UnblockStockBatch(stockRepository, auditLogger, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReserveStock reserveStock(StockRepository stockRepository) {
|
||||
return new ReserveStock(stockRepository);
|
||||
public ReserveStock reserveStock(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new ReserveStock(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReleaseReservation releaseReservation(StockRepository stockRepository) {
|
||||
return new ReleaseReservation(stockRepository);
|
||||
public ReleaseReservation releaseReservation(StockRepository stockRepository, UnitOfWork unitOfWork) {
|
||||
return new ReleaseReservation(stockRepository, unitOfWork);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -129,8 +130,8 @@ public class InventoryUseCaseConfiguration {
|
|||
// ==================== StockMovement Use Cases ====================
|
||||
|
||||
@Bean
|
||||
public RecordStockMovement recordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authorizationPort) {
|
||||
return new RecordStockMovement(stockMovementRepository, authorizationPort);
|
||||
public RecordStockMovement recordStockMovement(StockMovementRepository stockMovementRepository, AuthorizationPort authorizationPort, UnitOfWork unitOfWork) {
|
||||
return new RecordStockMovement(stockMovementRepository, authorizationPort, unitOfWork);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue