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

refactor(usermanagement,masterdata): UnitOfWork-Pattern + JdbcClient-Migration

Migriert UserManagement und MasterData BCs von JPA/Spring Data auf
JdbcClient + UnitOfWork, analog zum Production-BC. Inventory bleibt auf JPA.

- 6 neue JdbcClient-Repositories (User, Role, Article, Supplier, Customer, ProductCategory)
- 45 Use Cases: UoW für schreibende, @Transactional entfernt bei allen
- AbstractIntegrationTest + 20 Integration-Tests auf JdbcClient umgestellt
- 12 Unit-Test-Klassen mit UoW-Mock erweitert
- 34 alte JPA-Dateien gelöscht (Entities, Spring Data Repos, Adapter, Mapper)
This commit is contained in:
Sebastian Frick 2026-02-25 08:35:24 +01:00
parent e5bc5690da
commit 46275f6d59
117 changed files with 2258 additions and 3549 deletions

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class ActivateArticle {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public ActivateArticle(ArticleRepository articleRepository) {
public ActivateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(ArticleId articleId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class ActivateArticle {
}
article.activate();
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class ActivateCustomer {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public ActivateCustomer(CustomerRepository customerRepository) {
public ActivateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class ActivateCustomer {
}
customer.activate();
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class ActivateSupplier {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public ActivateSupplier(SupplierRepository supplierRepository) {
public ActivateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(SupplierId supplierId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class ActivateSupplier {
}
supplier.activate();
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.AddCertificateCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class AddCertificate {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public AddCertificate(SupplierRepository supplierRepository) {
public AddCertificate(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(AddCertificateCommand cmd, ActorId performedBy) {
@ -39,12 +40,13 @@ public class AddCertificate {
}
supplier.addCertificate(certificate);
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -4,18 +4,19 @@ import de.effigenix.application.masterdata.command.AddDeliveryAddressCommand;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Address;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class AddDeliveryAddress {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public AddDeliveryAddress(CustomerRepository customerRepository) {
public AddDeliveryAddress(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(AddDeliveryAddressCommand cmd, ActorId performedBy) {
@ -46,12 +47,13 @@ public class AddDeliveryAddress {
}
customer.addDeliveryAddress(deliveryAddress);
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -4,18 +4,19 @@ import de.effigenix.application.masterdata.command.AddSalesUnitCommand;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Money;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class AddSalesUnit {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public AddSalesUnit(ArticleRepository articleRepository) {
public AddSalesUnit(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(AddSalesUnitCommand cmd, ActorId performedBy) {
@ -50,12 +51,13 @@ public class AddSalesUnit {
case Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.AssignSupplierCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class AssignSupplier {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public AssignSupplier(ArticleRepository articleRepository) {
public AssignSupplier(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(AssignSupplierCommand cmd, ActorId performedBy) {
@ -33,12 +34,13 @@ public class AssignSupplier {
}
article.addSupplierReference(SupplierId.of(cmd.supplierId()));
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.CreateArticleCommand;
import de.effigenix.domain.masterdata.*;
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 CreateArticle {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public CreateArticle(ArticleRepository articleRepository) {
public CreateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(CreateArticleCommand cmd, ActorId performedBy) {
@ -37,12 +38,13 @@ public class CreateArticle {
}
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Result.Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.CreateCustomerCommand;
import de.effigenix.domain.masterdata.*;
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 CreateCustomer {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public CreateCustomer(CustomerRepository customerRepository) {
public CreateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(CreateCustomerCommand cmd, ActorId performedBy) {
@ -38,12 +39,13 @@ public class CreateCustomer {
}
}
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Result.Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.CreateProductCategoryCommand;
import de.effigenix.domain.masterdata.*;
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 CreateProductCategory {
private final ProductCategoryRepository categoryRepository;
private final UnitOfWork unitOfWork;
public CreateProductCategory(ProductCategoryRepository categoryRepository) {
public CreateProductCategory(ProductCategoryRepository categoryRepository, UnitOfWork unitOfWork) {
this.categoryRepository = categoryRepository;
this.unitOfWork = unitOfWork;
}
public Result<ProductCategoryError, ProductCategory> execute(CreateProductCategoryCommand cmd, ActorId performedBy) {
@ -34,12 +35,13 @@ public class CreateProductCategory {
}
}
return unitOfWork.executeAtomically(() -> {
switch (categoryRepository.save(category)) {
case Result.Failure(var err) ->
{ return Result.failure(new ProductCategoryError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(category);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.CreateSupplierCommand;
import de.effigenix.domain.masterdata.*;
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 CreateSupplier {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public CreateSupplier(SupplierRepository supplierRepository) {
public CreateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(CreateSupplierCommand cmd, ActorId performedBy) {
@ -42,12 +43,13 @@ public class CreateSupplier {
}
// 4. Speichern
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Result.Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class DeactivateArticle {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public DeactivateArticle(ArticleRepository articleRepository) {
public DeactivateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(ArticleId articleId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class DeactivateArticle {
}
article.deactivate();
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class DeactivateCustomer {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public DeactivateCustomer(CustomerRepository customerRepository) {
public DeactivateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class DeactivateCustomer {
}
customer.deactivate();
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class DeactivateSupplier {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public DeactivateSupplier(SupplierRepository supplierRepository) {
public DeactivateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(SupplierId supplierId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class DeactivateSupplier {
}
supplier.deactivate();
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -2,20 +2,21 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class DeleteProductCategory {
private final ProductCategoryRepository categoryRepository;
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public DeleteProductCategory(ProductCategoryRepository categoryRepository, ArticleRepository articleRepository) {
public DeleteProductCategory(ProductCategoryRepository categoryRepository, ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.categoryRepository = categoryRepository;
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ProductCategoryError, Void> execute(ProductCategoryId categoryId, ActorId performedBy) {
@ -31,6 +32,7 @@ public class DeleteProductCategory {
}
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.findByCategory(categoryId)) {
case Failure(var err) ->
{ return Result.failure(new ProductCategoryError.RepositoryFailure(err.message())); }
@ -48,5 +50,6 @@ public class DeleteProductCategory {
}
return Result.success(null);
});
}
}

View file

@ -2,11 +2,9 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class GetArticle {
private final ArticleRepository articleRepository;

View file

@ -2,11 +2,9 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class GetCustomer {
private final CustomerRepository customerRepository;

View file

@ -2,11 +2,9 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class GetSupplier {
private final SupplierRepository supplierRepository;

View file

@ -2,13 +2,11 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class ListArticles {
private final ArticleRepository articleRepository;

View file

@ -2,13 +2,11 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class ListCustomers {
private final CustomerRepository customerRepository;

View file

@ -2,13 +2,11 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class ListProductCategories {
private final ProductCategoryRepository categoryRepository;

View file

@ -2,13 +2,11 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Result;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static de.effigenix.shared.common.Result.*;
@Transactional(readOnly = true)
public class ListSuppliers {
private final SupplierRepository supplierRepository;

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.RateSupplierCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class RateSupplier {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public RateSupplier(SupplierRepository supplierRepository) {
public RateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(RateSupplierCommand cmd, ActorId performedBy) {
@ -39,12 +40,13 @@ public class RateSupplier {
}
supplier.rate(rating);
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -3,20 +3,21 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.RemoveCertificateCommand;
import de.effigenix.domain.masterdata.*;
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.util.Objects;
import static de.effigenix.shared.common.Result.*;
@Transactional
public class RemoveCertificate {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public RemoveCertificate(SupplierRepository supplierRepository) {
public RemoveCertificate(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(RemoveCertificateCommand cmd, ActorId performedBy) {
@ -45,12 +46,13 @@ public class RemoveCertificate {
}
supplier.removeCertificate(certificate.get());
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.RemoveDeliveryAddressCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class RemoveDeliveryAddress {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public RemoveDeliveryAddress(CustomerRepository customerRepository) {
public RemoveDeliveryAddress(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(RemoveDeliveryAddressCommand cmd, ActorId performedBy) {
@ -33,12 +34,13 @@ public class RemoveDeliveryAddress {
}
customer.removeDeliveryAddress(cmd.label());
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -2,18 +2,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class RemoveFrameContract {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public RemoveFrameContract(CustomerRepository customerRepository) {
public RemoveFrameContract(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
@ -30,12 +31,13 @@ public class RemoveFrameContract {
}
customer.removeFrameContract();
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.RemoveSalesUnitCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class RemoveSalesUnit {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public RemoveSalesUnit(ArticleRepository articleRepository) {
public RemoveSalesUnit(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(RemoveSalesUnitCommand cmd, ActorId performedBy) {
@ -37,12 +38,13 @@ public class RemoveSalesUnit {
case Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.RemoveSupplierCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class RemoveSupplier {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public RemoveSupplier(ArticleRepository articleRepository) {
public RemoveSupplier(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(RemoveSupplierCommand cmd, ActorId performedBy) {
@ -33,12 +34,13 @@ public class RemoveSupplier {
}
article.removeSupplierReference(SupplierId.of(cmd.supplierId()));
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -4,20 +4,21 @@ import de.effigenix.application.masterdata.command.SetFrameContractCommand;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Money;
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.util.ArrayList;
import static de.effigenix.shared.common.Result.*;
@Transactional
public class SetFrameContract {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public SetFrameContract(CustomerRepository customerRepository) {
public SetFrameContract(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(SetFrameContractCommand cmd, ActorId performedBy) {
@ -62,12 +63,13 @@ public class SetFrameContract {
case Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -3,18 +3,19 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.SetPreferencesCommand;
import de.effigenix.domain.masterdata.*;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class SetPreferences {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public SetPreferences(CustomerRepository customerRepository) {
public SetPreferences(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(SetPreferencesCommand cmd, ActorId performedBy) {
@ -33,12 +34,13 @@ public class SetPreferences {
}
customer.setPreferences(cmd.preferences());
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.UpdateArticleCommand;
import de.effigenix.domain.masterdata.*;
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 UpdateArticle {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public UpdateArticle(ArticleRepository articleRepository) {
public UpdateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(UpdateArticleCommand cmd, ActorId performedBy) {
@ -36,12 +37,13 @@ public class UpdateArticle {
case Result.Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Result.Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.UpdateCustomerCommand;
import de.effigenix.domain.masterdata.*;
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 UpdateCustomer {
private final CustomerRepository customerRepository;
private final UnitOfWork unitOfWork;
public UpdateCustomer(CustomerRepository customerRepository) {
public UpdateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
this.customerRepository = customerRepository;
this.unitOfWork = unitOfWork;
}
public Result<CustomerError, Customer> execute(UpdateCustomerCommand cmd, ActorId performedBy) {
@ -40,12 +41,13 @@ public class UpdateCustomer {
case Result.Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (customerRepository.save(customer)) {
case Result.Failure(var err) ->
{ return Result.failure(new CustomerError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(customer);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.UpdateProductCategoryCommand;
import de.effigenix.domain.masterdata.*;
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 UpdateProductCategory {
private final ProductCategoryRepository categoryRepository;
private final UnitOfWork unitOfWork;
public UpdateProductCategory(ProductCategoryRepository categoryRepository) {
public UpdateProductCategory(ProductCategoryRepository categoryRepository, UnitOfWork unitOfWork) {
this.categoryRepository = categoryRepository;
this.unitOfWork = unitOfWork;
}
public Result<ProductCategoryError, ProductCategory> execute(UpdateProductCategoryCommand cmd, ActorId performedBy) {
@ -36,12 +37,13 @@ public class UpdateProductCategory {
case Result.Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (categoryRepository.save(category)) {
case Result.Failure(var err) ->
{ return Result.failure(new ProductCategoryError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(category);
});
}
}

View file

@ -4,18 +4,19 @@ import de.effigenix.application.masterdata.command.UpdateSalesUnitPriceCommand;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Money;
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 static de.effigenix.shared.common.Result.*;
@Transactional
public class UpdateSalesUnitPrice {
private final ArticleRepository articleRepository;
private final UnitOfWork unitOfWork;
public UpdateSalesUnitPrice(ArticleRepository articleRepository) {
public UpdateSalesUnitPrice(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
this.articleRepository = articleRepository;
this.unitOfWork = unitOfWork;
}
public Result<ArticleError, Article> execute(UpdateSalesUnitPriceCommand cmd, ActorId performedBy) {
@ -44,12 +45,13 @@ public class UpdateSalesUnitPrice {
case Success(var ignored) -> { }
}
return unitOfWork.executeAtomically(() -> {
switch (articleRepository.save(article)) {
case Failure(var err) ->
{ return Result.failure(new ArticleError.RepositoryFailure(err.message())); }
case Success(var ignored) -> { }
}
return Result.success(article);
});
}
}

View file

@ -3,16 +3,17 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.UpdateSupplierCommand;
import de.effigenix.domain.masterdata.*;
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 UpdateSupplier {
private final SupplierRepository supplierRepository;
private final UnitOfWork unitOfWork;
public UpdateSupplier(SupplierRepository supplierRepository) {
public UpdateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
this.supplierRepository = supplierRepository;
this.unitOfWork = unitOfWork;
}
public Result<SupplierError, Supplier> execute(UpdateSupplierCommand cmd, ActorId performedBy) {
@ -43,12 +44,13 @@ public class UpdateSupplier {
}
// 3. Speichern
return unitOfWork.executeAtomically(() -> {
switch (supplierRepository.save(supplier)) {
case Result.Failure(var err) ->
{ return Result.failure(new SupplierError.RepositoryFailure(err.message())); }
case Result.Success(var ignored) -> { }
}
return Result.success(supplier);
});
}
}

View file

@ -6,32 +6,34 @@ import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
/**
* Use Case: Assign a role to a user.
*/
@Transactional
public class AssignRole {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public AssignRole(
UserRepository userRepository,
RoleRepository roleRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(AssignRoleCommand cmd, ActorId performedBy) {
@ -61,12 +63,13 @@ public class AssignRole {
}
Role role = optRole.get();
return user.assignRole(role)
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.ROLE_ASSIGNED, updated.id().value(), "Role: " + role.name(), performedBy);
return UserDTO.from(updated);
}));
})));
});
}

View file

@ -4,8 +4,8 @@ import de.effigenix.application.usermanagement.command.AuthenticateCommand;
import de.effigenix.application.usermanagement.dto.SessionToken;
import de.effigenix.domain.usermanagement.*;
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.OffsetDateTime;
import java.time.ZoneOffset;
@ -17,24 +17,26 @@ import java.util.Optional;
* Returns a JWT session token on success.
* Logs all authentication attempts for security auditing.
*/
@Transactional
public class AuthenticateUser {
private final UserRepository userRepository;
private final PasswordHasher passwordHasher;
private final SessionManager sessionManager;
private final AuditLogger auditLogger;
private final UnitOfWork unitOfWork;
public AuthenticateUser(
UserRepository userRepository,
PasswordHasher passwordHasher,
SessionManager sessionManager,
AuditLogger auditLogger
AuditLogger auditLogger,
UnitOfWork unitOfWork
) {
this.userRepository = userRepository;
this.passwordHasher = passwordHasher;
this.sessionManager = sessionManager;
this.auditLogger = auditLogger;
this.unitOfWork = unitOfWork;
}
public Result<UserError, SessionToken> execute(AuthenticateCommand cmd) {
@ -73,11 +75,12 @@ public class AuthenticateUser {
// 5. Update last login timestamp (immutable)
return user.withLastLogin(OffsetDateTime.now(ZoneOffset.UTC))
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.LOGIN_SUCCESS, updated.id().value(), ActorId.of(updated.id().value()));
return token;
}));
})));
}
}

View file

@ -4,8 +4,8 @@ import de.effigenix.application.usermanagement.command.ChangePasswordCommand;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Change user password.
@ -13,24 +13,26 @@ import org.springframework.transaction.annotation.Transactional;
* Requires current password verification for security.
* Self-service: users can change their own password without PASSWORD_CHANGE permission.
*/
@Transactional
public class ChangePassword {
private final UserRepository userRepository;
private final PasswordHasher passwordHasher;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public ChangePassword(
UserRepository userRepository,
PasswordHasher passwordHasher,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
this.userRepository = userRepository;
this.passwordHasher = passwordHasher;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, Void> execute(ChangePasswordCommand cmd, ActorId performedBy) {
@ -71,12 +73,13 @@ public class ChangePassword {
// 5. Hash and update (immutable)
PasswordHash newPasswordHash = passwordHasher.hash(cmd.newPassword());
return user.changePassword(newPasswordHash)
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.PASSWORD_CHANGED, updated.id().value(), performedBy);
return null;
}));
})));
}
private Result<UserError, User> findUser(UserId userId) {

View file

@ -6,8 +6,8 @@ import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
@ -15,7 +15,6 @@ import java.util.Set;
/**
* Use Case: Create a new user account.
*/
@Transactional
public class CreateUser {
private final UserRepository userRepository;
@ -23,19 +22,22 @@ public class CreateUser {
private final PasswordHasher passwordHasher;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public CreateUser(
UserRepository userRepository,
RoleRepository roleRepository,
PasswordHasher passwordHasher,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordHasher = passwordHasher;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(CreateUserCommand cmd, ActorId performedBy) {
@ -99,11 +101,12 @@ public class CreateUser {
}
return User.create(cmd.username(), cmd.email(), passwordHash, roles, cmd.branchId())
.flatMap(user -> userRepository.save(user)
.flatMap(user -> unitOfWork.executeAtomically(() ->
userRepository.save(user)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.USER_CREATED, user.id().value(), performedBy);
return UserDTO.from(user);
}));
})));
}
}

View file

@ -5,12 +5,10 @@ import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Get a single user by ID.
*/
@Transactional(readOnly = true)
public class GetUser {
private final UserRepository userRepository;

View file

@ -8,7 +8,6 @@ import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import de.effigenix.shared.security.BranchId;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@ -16,7 +15,6 @@ import java.util.stream.Collectors;
/**
* Use Case: List all users (with optional branch filtering).
*/
@Transactional(readOnly = true)
public class ListUsers {
private final UserRepository userRepository;

View file

@ -5,23 +5,24 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Lock a user account (prevent login).
*/
@Transactional
public class LockUser {
private final UserRepository userRepository;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public LockUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort) {
public LockUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort, UnitOfWork unitOfWork) {
this.userRepository = userRepository;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(LockUserCommand cmd, ActorId performedBy) {
@ -38,12 +39,13 @@ public class LockUser {
UserId userId = UserId.of(cmd.userId());
return findUser(userId)
.flatMap(User::lock)
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.USER_LOCKED, updated.id().value(), performedBy);
return UserDTO.from(updated);
}));
})));
}
private Result<UserError, User> findUser(UserId userId) {

View file

@ -5,30 +5,32 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Remove a role from a user.
*/
@Transactional
public class RemoveRole {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public RemoveRole(
UserRepository userRepository,
RoleRepository roleRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(RemoveRoleCommand cmd, ActorId performedBy) {
@ -58,12 +60,13 @@ public class RemoveRole {
}
Role role = optRole.get();
return user.removeRole(role)
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.ROLE_REMOVED, updated.id().value(), "Role: " + role.name(), performedBy);
return UserDTO.from(updated);
}));
})));
});
}

View file

@ -5,23 +5,24 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Unlock a user account (allow login).
*/
@Transactional
public class UnlockUser {
private final UserRepository userRepository;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public UnlockUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort) {
public UnlockUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort, UnitOfWork unitOfWork) {
this.userRepository = userRepository;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(UnlockUserCommand cmd, ActorId performedBy) {
@ -38,12 +39,13 @@ public class UnlockUser {
UserId userId = UserId.of(cmd.userId());
return findUser(userId)
.flatMap(User::unlock)
.flatMap(updated -> userRepository.save(updated)
.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.USER_UNLOCKED, updated.id().value(), performedBy);
return UserDTO.from(updated);
}));
})));
}
private Result<UserError, User> findUser(UserId userId) {

View file

@ -5,23 +5,24 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;
/**
* Use Case: Update user details (email, branch).
*/
@Transactional
public class UpdateUser {
private final UserRepository userRepository;
private final AuditLogger auditLogger;
private final AuthorizationPort authPort;
private final UnitOfWork unitOfWork;
public UpdateUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort) {
public UpdateUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort, UnitOfWork unitOfWork) {
this.userRepository = userRepository;
this.auditLogger = auditLogger;
this.authPort = authPort;
this.unitOfWork = unitOfWork;
}
public Result<UserError, UserDTO> execute(UpdateUserCommand cmd, ActorId performedBy) {
@ -54,12 +55,13 @@ public class UpdateUser {
current = current.flatMap(u -> u.updateBranch(cmd.branchId()));
}
return current.flatMap(updated -> userRepository.save(updated)
return current.flatMap(updated -> unitOfWork.executeAtomically(() ->
userRepository.save(updated)
.mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
.map(ignored -> {
auditLogger.log(AuditEvent.USER_UPDATED, updated.id().value(), performedBy);
return UserDTO.from(updated);
}));
})));
}
private Result<UserError, User> findUser(UserId userId) {

View file

@ -5,6 +5,7 @@ import de.effigenix.domain.masterdata.ArticleRepository;
import de.effigenix.domain.masterdata.CustomerRepository;
import de.effigenix.domain.masterdata.ProductCategoryRepository;
import de.effigenix.domain.masterdata.SupplierRepository;
import de.effigenix.shared.persistence.UnitOfWork;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -14,13 +15,13 @@ public class MasterDataUseCaseConfiguration {
// ==================== Article Use Cases ====================
@Bean
public CreateArticle createArticle(ArticleRepository articleRepository) {
return new CreateArticle(articleRepository);
public CreateArticle createArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new CreateArticle(articleRepository, unitOfWork);
}
@Bean
public UpdateArticle updateArticle(ArticleRepository articleRepository) {
return new UpdateArticle(articleRepository);
public UpdateArticle updateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new UpdateArticle(articleRepository, unitOfWork);
}
@Bean
@ -34,50 +35,50 @@ public class MasterDataUseCaseConfiguration {
}
@Bean
public ActivateArticle activateArticle(ArticleRepository articleRepository) {
return new ActivateArticle(articleRepository);
public ActivateArticle activateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new ActivateArticle(articleRepository, unitOfWork);
}
@Bean
public DeactivateArticle deactivateArticle(ArticleRepository articleRepository) {
return new DeactivateArticle(articleRepository);
public DeactivateArticle deactivateArticle(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new DeactivateArticle(articleRepository, unitOfWork);
}
@Bean
public AddSalesUnit addSalesUnit(ArticleRepository articleRepository) {
return new AddSalesUnit(articleRepository);
public AddSalesUnit addSalesUnit(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new AddSalesUnit(articleRepository, unitOfWork);
}
@Bean
public RemoveSalesUnit removeSalesUnit(ArticleRepository articleRepository) {
return new RemoveSalesUnit(articleRepository);
public RemoveSalesUnit removeSalesUnit(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new RemoveSalesUnit(articleRepository, unitOfWork);
}
@Bean
public UpdateSalesUnitPrice updateSalesUnitPrice(ArticleRepository articleRepository) {
return new UpdateSalesUnitPrice(articleRepository);
public UpdateSalesUnitPrice updateSalesUnitPrice(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new UpdateSalesUnitPrice(articleRepository, unitOfWork);
}
@Bean
public AssignSupplier assignSupplier(ArticleRepository articleRepository) {
return new AssignSupplier(articleRepository);
public AssignSupplier assignSupplier(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new AssignSupplier(articleRepository, unitOfWork);
}
@Bean
public RemoveSupplier removeSupplier(ArticleRepository articleRepository) {
return new RemoveSupplier(articleRepository);
public RemoveSupplier removeSupplier(ArticleRepository articleRepository, UnitOfWork unitOfWork) {
return new RemoveSupplier(articleRepository, unitOfWork);
}
// ==================== ProductCategory Use Cases ====================
@Bean
public CreateProductCategory createProductCategory(ProductCategoryRepository categoryRepository) {
return new CreateProductCategory(categoryRepository);
public CreateProductCategory createProductCategory(ProductCategoryRepository categoryRepository, UnitOfWork unitOfWork) {
return new CreateProductCategory(categoryRepository, unitOfWork);
}
@Bean
public UpdateProductCategory updateProductCategory(ProductCategoryRepository categoryRepository) {
return new UpdateProductCategory(categoryRepository);
public UpdateProductCategory updateProductCategory(ProductCategoryRepository categoryRepository, UnitOfWork unitOfWork) {
return new UpdateProductCategory(categoryRepository, unitOfWork);
}
@Bean
@ -88,21 +89,22 @@ public class MasterDataUseCaseConfiguration {
@Bean
public DeleteProductCategory deleteProductCategory(
ProductCategoryRepository categoryRepository,
ArticleRepository articleRepository
ArticleRepository articleRepository,
UnitOfWork unitOfWork
) {
return new DeleteProductCategory(categoryRepository, articleRepository);
return new DeleteProductCategory(categoryRepository, articleRepository, unitOfWork);
}
// ==================== Supplier Use Cases ====================
@Bean
public CreateSupplier createSupplier(SupplierRepository supplierRepository) {
return new CreateSupplier(supplierRepository);
public CreateSupplier createSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new CreateSupplier(supplierRepository, unitOfWork);
}
@Bean
public UpdateSupplier updateSupplier(SupplierRepository supplierRepository) {
return new UpdateSupplier(supplierRepository);
public UpdateSupplier updateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new UpdateSupplier(supplierRepository, unitOfWork);
}
@Bean
@ -116,40 +118,40 @@ public class MasterDataUseCaseConfiguration {
}
@Bean
public ActivateSupplier activateSupplier(SupplierRepository supplierRepository) {
return new ActivateSupplier(supplierRepository);
public ActivateSupplier activateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new ActivateSupplier(supplierRepository, unitOfWork);
}
@Bean
public DeactivateSupplier deactivateSupplier(SupplierRepository supplierRepository) {
return new DeactivateSupplier(supplierRepository);
public DeactivateSupplier deactivateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new DeactivateSupplier(supplierRepository, unitOfWork);
}
@Bean
public RateSupplier rateSupplier(SupplierRepository supplierRepository) {
return new RateSupplier(supplierRepository);
public RateSupplier rateSupplier(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new RateSupplier(supplierRepository, unitOfWork);
}
@Bean
public AddCertificate addCertificate(SupplierRepository supplierRepository) {
return new AddCertificate(supplierRepository);
public AddCertificate addCertificate(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new AddCertificate(supplierRepository, unitOfWork);
}
@Bean
public RemoveCertificate removeCertificate(SupplierRepository supplierRepository) {
return new RemoveCertificate(supplierRepository);
public RemoveCertificate removeCertificate(SupplierRepository supplierRepository, UnitOfWork unitOfWork) {
return new RemoveCertificate(supplierRepository, unitOfWork);
}
// ==================== Customer Use Cases ====================
@Bean
public CreateCustomer createCustomer(CustomerRepository customerRepository) {
return new CreateCustomer(customerRepository);
public CreateCustomer createCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new CreateCustomer(customerRepository, unitOfWork);
}
@Bean
public UpdateCustomer updateCustomer(CustomerRepository customerRepository) {
return new UpdateCustomer(customerRepository);
public UpdateCustomer updateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new UpdateCustomer(customerRepository, unitOfWork);
}
@Bean
@ -163,37 +165,37 @@ public class MasterDataUseCaseConfiguration {
}
@Bean
public ActivateCustomer activateCustomer(CustomerRepository customerRepository) {
return new ActivateCustomer(customerRepository);
public ActivateCustomer activateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new ActivateCustomer(customerRepository, unitOfWork);
}
@Bean
public DeactivateCustomer deactivateCustomer(CustomerRepository customerRepository) {
return new DeactivateCustomer(customerRepository);
public DeactivateCustomer deactivateCustomer(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new DeactivateCustomer(customerRepository, unitOfWork);
}
@Bean
public AddDeliveryAddress addDeliveryAddress(CustomerRepository customerRepository) {
return new AddDeliveryAddress(customerRepository);
public AddDeliveryAddress addDeliveryAddress(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new AddDeliveryAddress(customerRepository, unitOfWork);
}
@Bean
public RemoveDeliveryAddress removeDeliveryAddress(CustomerRepository customerRepository) {
return new RemoveDeliveryAddress(customerRepository);
public RemoveDeliveryAddress removeDeliveryAddress(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new RemoveDeliveryAddress(customerRepository, unitOfWork);
}
@Bean
public SetFrameContract setFrameContract(CustomerRepository customerRepository) {
return new SetFrameContract(customerRepository);
public SetFrameContract setFrameContract(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new SetFrameContract(customerRepository, unitOfWork);
}
@Bean
public RemoveFrameContract removeFrameContract(CustomerRepository customerRepository) {
return new RemoveFrameContract(customerRepository);
public RemoveFrameContract removeFrameContract(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new RemoveFrameContract(customerRepository, unitOfWork);
}
@Bean
public SetPreferences setPreferences(CustomerRepository customerRepository) {
return new SetPreferences(customerRepository);
public SetPreferences setPreferences(CustomerRepository customerRepository, UnitOfWork unitOfWork) {
return new SetPreferences(customerRepository, unitOfWork);
}
}

View file

@ -3,6 +3,7 @@ package de.effigenix.infrastructure.config;
import de.effigenix.application.usermanagement.*;
import de.effigenix.domain.usermanagement.RoleRepository;
import de.effigenix.domain.usermanagement.UserRepository;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -22,9 +23,10 @@ public class UseCaseConfiguration {
RoleRepository roleRepository,
PasswordHasher passwordHasher,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new CreateUser(userRepository, roleRepository, passwordHasher, auditLogger, authPort);
return new CreateUser(userRepository, roleRepository, passwordHasher, auditLogger, authPort, unitOfWork);
}
@Bean
@ -32,9 +34,10 @@ public class UseCaseConfiguration {
UserRepository userRepository,
PasswordHasher passwordHasher,
SessionManager sessionManager,
AuditLogger auditLogger
AuditLogger auditLogger,
UnitOfWork unitOfWork
) {
return new AuthenticateUser(userRepository, passwordHasher, sessionManager, auditLogger);
return new AuthenticateUser(userRepository, passwordHasher, sessionManager, auditLogger, unitOfWork);
}
@Bean
@ -42,18 +45,20 @@ public class UseCaseConfiguration {
UserRepository userRepository,
PasswordHasher passwordHasher,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new ChangePassword(userRepository, passwordHasher, auditLogger, authPort);
return new ChangePassword(userRepository, passwordHasher, auditLogger, authPort, unitOfWork);
}
@Bean
public UpdateUser updateUser(
UserRepository userRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new UpdateUser(userRepository, auditLogger, authPort);
return new UpdateUser(userRepository, auditLogger, authPort, unitOfWork);
}
@Bean
@ -61,9 +66,10 @@ public class UseCaseConfiguration {
UserRepository userRepository,
RoleRepository roleRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new AssignRole(userRepository, roleRepository, auditLogger, authPort);
return new AssignRole(userRepository, roleRepository, auditLogger, authPort, unitOfWork);
}
@Bean
@ -71,27 +77,30 @@ public class UseCaseConfiguration {
UserRepository userRepository,
RoleRepository roleRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new RemoveRole(userRepository, roleRepository, auditLogger, authPort);
return new RemoveRole(userRepository, roleRepository, auditLogger, authPort, unitOfWork);
}
@Bean
public LockUser lockUser(
UserRepository userRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new LockUser(userRepository, auditLogger, authPort);
return new LockUser(userRepository, auditLogger, authPort, unitOfWork);
}
@Bean
public UnlockUser unlockUser(
UserRepository userRepository,
AuditLogger auditLogger,
AuthorizationPort authPort
AuthorizationPort authPort,
UnitOfWork unitOfWork
) {
return new UnlockUser(userRepository, auditLogger, authPort);
return new UnlockUser(userRepository, auditLogger, authPort, unitOfWork);
}
@Bean

View file

@ -0,0 +1,239 @@
package de.effigenix.infrastructure.masterdata.persistence;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Money;
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.sql.ResultSet;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.*;
@Repository
@Profile("!no-db")
public class JdbcArticleRepository implements ArticleRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcArticleRepository.class);
private final JdbcClient jdbc;
public JdbcArticleRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Override
public Result<RepositoryError, Optional<Article>> findById(ArticleId id) {
try {
var articleOpt = jdbc.sql("SELECT * FROM articles WHERE id = :id")
.param("id", id.value())
.query(this::mapArticleRow)
.optional();
if (articleOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadChildren(articleOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Article>> findAll() {
try {
var articles = jdbc.sql("SELECT * FROM articles ORDER BY name")
.query(this::mapArticleRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(articles);
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Article>> findByCategory(ProductCategoryId categoryId) {
try {
var articles = jdbc.sql("SELECT * FROM articles WHERE category_id = :categoryId ORDER BY name")
.param("categoryId", categoryId.value())
.query(this::mapArticleRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(articles);
} catch (Exception e) {
logger.trace("Database error in findByCategory", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Article>> findByStatus(ArticleStatus status) {
try {
var articles = jdbc.sql("SELECT * FROM articles WHERE status = :status ORDER BY name")
.param("status", status.name())
.query(this::mapArticleRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(articles);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> save(Article article) {
try {
int rows = jdbc.sql("""
UPDATE articles
SET name = :name, article_number = :articleNumber, category_id = :categoryId,
status = :status, updated_at = :updatedAt
WHERE id = :id
""")
.param("id", article.id().value())
.param("name", article.name().value())
.param("articleNumber", article.articleNumber().value())
.param("categoryId", article.categoryId().value())
.param("status", article.status().name())
.param("updatedAt", article.updatedAt())
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO articles (id, name, article_number, category_id, status, created_at, updated_at)
VALUES (:id, :name, :articleNumber, :categoryId, :status, :createdAt, :updatedAt)
""")
.param("id", article.id().value())
.param("name", article.name().value())
.param("articleNumber", article.articleNumber().value())
.param("categoryId", article.categoryId().value())
.param("status", article.status().name())
.param("createdAt", article.createdAt())
.param("updatedAt", article.updatedAt())
.update();
}
saveSalesUnits(article);
saveSupplierReferences(article);
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(Article article) {
try {
jdbc.sql("DELETE FROM articles WHERE id = :id")
.param("id", article.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByArticleNumber(ArticleNumber articleNumber) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM articles WHERE article_number = :articleNumber")
.param("articleNumber", articleNumber.value())
.query(Integer.class)
.single();
return Result.success(count > 0);
} catch (Exception e) {
logger.trace("Database error in existsByArticleNumber", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
private void saveSalesUnits(Article article) {
jdbc.sql("DELETE FROM sales_units WHERE article_id = :articleId")
.param("articleId", article.id().value())
.update();
for (SalesUnit su : article.salesUnits()) {
jdbc.sql("""
INSERT INTO sales_units (id, article_id, unit, price_model, price_amount, price_currency)
VALUES (:id, :articleId, :unit, :priceModel, :priceAmount, :priceCurrency)
""")
.param("id", su.id().value())
.param("articleId", article.id().value())
.param("unit", su.unit().name())
.param("priceModel", su.priceModel().name())
.param("priceAmount", su.price().amount())
.param("priceCurrency", su.price().currency().getCurrencyCode())
.update();
}
}
private void saveSupplierReferences(Article article) {
jdbc.sql("DELETE FROM article_suppliers WHERE article_id = :articleId")
.param("articleId", article.id().value())
.update();
for (SupplierId supplierId : article.supplierReferences()) {
jdbc.sql("INSERT INTO article_suppliers (article_id, supplier_id) VALUES (:articleId, :supplierId)")
.param("articleId", article.id().value())
.param("supplierId", supplierId.value())
.update();
}
}
private Article loadChildren(Article article) {
var salesUnits = jdbc.sql("SELECT * FROM sales_units WHERE article_id = :articleId")
.param("articleId", article.id().value())
.query(this::mapSalesUnitRow)
.list();
var supplierIds = jdbc.sql("SELECT supplier_id FROM article_suppliers WHERE article_id = :articleId")
.param("articleId", article.id().value())
.query((rs, rowNum) -> SupplierId.of(rs.getString("supplier_id")))
.set();
return Article.reconstitute(
article.id(), article.name(), article.articleNumber(), article.categoryId(),
salesUnits, article.status(), supplierIds, article.createdAt(), article.updatedAt()
);
}
private Article mapArticleRow(ResultSet rs, int rowNum) throws SQLException {
return Article.reconstitute(
ArticleId.of(rs.getString("id")),
new ArticleName(rs.getString("name")),
new ArticleNumber(rs.getString("article_number")),
ProductCategoryId.of(rs.getString("category_id")),
List.of(),
ArticleStatus.valueOf(rs.getString("status")),
Set.of(),
rs.getObject("created_at", OffsetDateTime.class),
rs.getObject("updated_at", OffsetDateTime.class)
);
}
private SalesUnit mapSalesUnitRow(ResultSet rs, int rowNum) throws SQLException {
return SalesUnit.reconstitute(
SalesUnitId.of(rs.getString("id")),
Unit.valueOf(rs.getString("unit")),
PriceModel.valueOf(rs.getString("price_model")),
new Money(rs.getBigDecimal("price_amount"),
java.util.Currency.getInstance(rs.getString("price_currency")))
);
}
}

View file

@ -0,0 +1,382 @@
package de.effigenix.infrastructure.masterdata.persistence;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.*;
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.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.*;
@Repository
@Profile("!no-db")
public class JdbcCustomerRepository implements CustomerRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcCustomerRepository.class);
private final JdbcClient jdbc;
public JdbcCustomerRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Override
public Result<RepositoryError, Optional<Customer>> findById(CustomerId id) {
try {
var customerOpt = jdbc.sql("SELECT * FROM customers WHERE id = :id")
.param("id", id.value())
.query(this::mapCustomerRow)
.optional();
if (customerOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadChildren(customerOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findAll() {
try {
var customers = jdbc.sql("SELECT * FROM customers ORDER BY name")
.query(this::mapCustomerRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(customers);
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findByType(CustomerType type) {
try {
var customers = jdbc.sql("SELECT * FROM customers WHERE type = :type ORDER BY name")
.param("type", type.name())
.query(this::mapCustomerRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(customers);
} catch (Exception e) {
logger.trace("Database error in findByType", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findByStatus(CustomerStatus status) {
try {
var customers = jdbc.sql("SELECT * FROM customers WHERE status = :status ORDER BY name")
.param("status", status.name())
.query(this::mapCustomerRow)
.list()
.stream()
.map(this::loadChildren)
.toList();
return Result.success(customers);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> save(Customer customer) {
try {
int rows = jdbc.sql("""
UPDATE customers
SET name = :name, type = :type, phone = :phone, email = :email,
contact_person = :contactPerson,
billing_street = :billingStreet, billing_house_number = :billingHouseNumber,
billing_postal_code = :billingPostalCode, billing_city = :billingCity,
billing_country = :billingCountry,
payment_due_days = :paymentDueDays, payment_description = :paymentDescription,
status = :status, updated_at = :updatedAt
WHERE id = :id
""")
.param("id", customer.id().value())
.params(customerParams(customer))
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO customers
(id, name, type, phone, email, contact_person,
billing_street, billing_house_number, billing_postal_code, billing_city, billing_country,
payment_due_days, payment_description, status, created_at, updated_at)
VALUES (:id, :name, :type, :phone, :email, :contactPerson,
:billingStreet, :billingHouseNumber, :billingPostalCode, :billingCity, :billingCountry,
:paymentDueDays, :paymentDescription, :status, :createdAt, :updatedAt)
""")
.param("id", customer.id().value())
.param("createdAt", customer.createdAt())
.params(customerParams(customer))
.update();
}
saveDeliveryAddresses(customer);
savePreferences(customer);
saveFrameContract(customer);
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(Customer customer) {
try {
jdbc.sql("DELETE FROM frame_contracts WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.update();
jdbc.sql("DELETE FROM customers WHERE id = :id")
.param("id", customer.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(CustomerName name) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM customers 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()));
}
}
private Map<String, Object> customerParams(Customer customer) {
var params = new LinkedHashMap<String, Object>();
params.put("name", customer.name().value());
params.put("type", customer.type().name());
params.put("phone", customer.contactInfo() != null ? customer.contactInfo().phone() : null);
params.put("email", customer.contactInfo() != null ? customer.contactInfo().email() : null);
params.put("contactPerson", customer.contactInfo() != null ? customer.contactInfo().contactPerson() : null);
params.put("billingStreet", customer.billingAddress().street());
params.put("billingHouseNumber", customer.billingAddress().houseNumber());
params.put("billingPostalCode", customer.billingAddress().postalCode());
params.put("billingCity", customer.billingAddress().city());
params.put("billingCountry", customer.billingAddress().country());
params.put("paymentDueDays", customer.paymentTerms() != null ? customer.paymentTerms().paymentDueDays() : null);
params.put("paymentDescription", customer.paymentTerms() != null ? customer.paymentTerms().description() : null);
params.put("status", customer.status().name());
params.put("updatedAt", customer.updatedAt());
return params;
}
private void saveDeliveryAddresses(Customer customer) {
jdbc.sql("DELETE FROM delivery_addresses WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.update();
for (DeliveryAddress da : customer.deliveryAddresses()) {
jdbc.sql("""
INSERT INTO delivery_addresses
(customer_id, label, street, house_number, postal_code, city, country, contact_person, delivery_notes)
VALUES (:customerId, :label, :street, :houseNumber, :postalCode, :city, :country, :contactPerson, :deliveryNotes)
""")
.param("customerId", customer.id().value())
.param("label", da.label())
.param("street", da.address().street())
.param("houseNumber", da.address().houseNumber())
.param("postalCode", da.address().postalCode())
.param("city", da.address().city())
.param("country", da.address().country())
.param("contactPerson", da.contactPerson())
.param("deliveryNotes", da.deliveryNotes())
.update();
}
}
private void savePreferences(Customer customer) {
jdbc.sql("DELETE FROM customer_preferences WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.update();
for (CustomerPreference pref : customer.preferences()) {
jdbc.sql("INSERT INTO customer_preferences (customer_id, preference) VALUES (:customerId, :preference)")
.param("customerId", customer.id().value())
.param("preference", pref.name())
.update();
}
}
private void saveFrameContract(Customer customer) {
jdbc.sql("DELETE FROM frame_contracts WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.update();
if (customer.frameContract() != null) {
var fc = customer.frameContract();
jdbc.sql("""
INSERT INTO frame_contracts (id, customer_id, valid_from, valid_until, delivery_rhythm)
VALUES (:id, :customerId, :validFrom, :validUntil, :deliveryRhythm)
""")
.param("id", fc.id().value())
.param("customerId", customer.id().value())
.param("validFrom", fc.validFrom())
.param("validUntil", fc.validUntil())
.param("deliveryRhythm", fc.deliveryRhythm().name())
.update();
saveContractLineItems(fc);
}
}
private void saveContractLineItems(FrameContract fc) {
jdbc.sql("DELETE FROM contract_line_items WHERE frame_contract_id = :fcId")
.param("fcId", fc.id().value())
.update();
for (ContractLineItem item : fc.lineItems()) {
jdbc.sql("""
INSERT INTO contract_line_items
(frame_contract_id, article_id, agreed_price_amount, agreed_price_currency, agreed_quantity, unit)
VALUES (:fcId, :articleId, :priceAmount, :priceCurrency, :quantity, :unit)
""")
.param("fcId", fc.id().value())
.param("articleId", item.articleId().value())
.param("priceAmount", item.agreedPrice().amount())
.param("priceCurrency", item.agreedPrice().currency().getCurrencyCode())
.param("quantity", item.agreedQuantity())
.param("unit", item.unit() != null ? item.unit().name() : null)
.update();
}
}
private Customer loadChildren(Customer customer) {
var deliveryAddresses = jdbc.sql(
"SELECT * FROM delivery_addresses WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.query(this::mapDeliveryAddressRow)
.list();
var preferences = jdbc.sql(
"SELECT preference FROM customer_preferences WHERE customer_id = :customerId")
.param("customerId", customer.id().value())
.query((rs, rowNum) -> CustomerPreference.valueOf(rs.getString("preference")))
.set();
FrameContract frameContract = loadFrameContract(customer.id().value());
return Customer.reconstitute(
customer.id(), customer.name(), customer.type(), customer.billingAddress(),
customer.contactInfo(), customer.paymentTerms(), deliveryAddresses,
frameContract, preferences, customer.status(), customer.createdAt(), customer.updatedAt()
);
}
private FrameContract loadFrameContract(String customerId) {
var fcOpt = jdbc.sql("SELECT * FROM frame_contracts WHERE customer_id = :customerId")
.param("customerId", customerId)
.query(this::mapFrameContractRow)
.optional();
if (fcOpt.isEmpty()) {
return null;
}
var fc = fcOpt.get();
var lineItems = jdbc.sql(
"SELECT * FROM contract_line_items WHERE frame_contract_id = :fcId")
.param("fcId", fc.id().value())
.query(this::mapContractLineItemRow)
.list();
return FrameContract.reconstitute(fc.id(), fc.validFrom(), fc.validUntil(),
fc.deliveryRhythm(), lineItems);
}
private Customer mapCustomerRow(ResultSet rs, int rowNum) throws SQLException {
Address billingAddress = new Address(
rs.getString("billing_street"),
rs.getString("billing_house_number"),
rs.getString("billing_postal_code"),
rs.getString("billing_city"),
rs.getString("billing_country")
);
ContactInfo contactInfo = new ContactInfo(
rs.getString("phone"), rs.getString("email"), rs.getString("contact_person"));
PaymentTerms paymentTerms = null;
Integer dueDays = rs.getObject("payment_due_days", Integer.class);
if (dueDays != null) {
paymentTerms = new PaymentTerms(dueDays, rs.getString("payment_description"));
}
return Customer.reconstitute(
CustomerId.of(rs.getString("id")),
new CustomerName(rs.getString("name")),
CustomerType.valueOf(rs.getString("type")),
billingAddress,
contactInfo,
paymentTerms,
List.of(),
null,
Set.of(),
CustomerStatus.valueOf(rs.getString("status")),
rs.getObject("created_at", OffsetDateTime.class),
rs.getObject("updated_at", OffsetDateTime.class)
);
}
private DeliveryAddress mapDeliveryAddressRow(ResultSet rs, int rowNum) throws SQLException {
Address address = new Address(
rs.getString("street"),
rs.getString("house_number"),
rs.getString("postal_code"),
rs.getString("city"),
rs.getString("country")
);
return new DeliveryAddress(
rs.getString("label"), address,
rs.getString("contact_person"), rs.getString("delivery_notes")
);
}
private FrameContract mapFrameContractRow(ResultSet rs, int rowNum) throws SQLException {
return FrameContract.reconstitute(
FrameContractId.of(rs.getString("id")),
rs.getObject("valid_from", LocalDate.class),
rs.getObject("valid_until", LocalDate.class),
DeliveryRhythm.valueOf(rs.getString("delivery_rhythm")),
List.of()
);
}
private ContractLineItem mapContractLineItemRow(ResultSet rs, int rowNum) throws SQLException {
String unitStr = rs.getString("unit");
return new ContractLineItem(
ArticleId.of(rs.getString("article_id")),
new Money(rs.getBigDecimal("agreed_price_amount"),
Currency.getInstance(rs.getString("agreed_price_currency"))),
rs.getObject("agreed_quantity", BigDecimal.class),
unitStr != null ? Unit.valueOf(unitStr) : null
);
}
}

View file

@ -0,0 +1,120 @@
package de.effigenix.infrastructure.masterdata.persistence;
import de.effigenix.domain.masterdata.*;
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.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
@Repository
@Profile("!no-db")
public class JdbcProductCategoryRepository implements ProductCategoryRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcProductCategoryRepository.class);
private final JdbcClient jdbc;
public JdbcProductCategoryRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Override
public Result<RepositoryError, Optional<ProductCategory>> findById(ProductCategoryId id) {
try {
var result = jdbc.sql("SELECT * FROM product_categories 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<ProductCategory>> findAll() {
try {
var result = jdbc.sql("SELECT * FROM product_categories 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, Void> save(ProductCategory category) {
try {
int rows = jdbc.sql("""
UPDATE product_categories SET name = :name, description = :description
WHERE id = :id
""")
.param("id", category.id().value())
.param("name", category.name().value())
.param("description", category.description())
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO product_categories (id, name, description)
VALUES (:id, :name, :description)
""")
.param("id", category.id().value())
.param("name", category.name().value())
.param("description", category.description())
.update();
}
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(ProductCategory category) {
try {
jdbc.sql("DELETE FROM product_categories WHERE id = :id")
.param("id", category.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(CategoryName name) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM product_categories 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()));
}
}
private ProductCategory mapRow(ResultSet rs, int rowNum) throws SQLException {
return ProductCategory.reconstitute(
ProductCategoryId.of(rs.getString("id")),
new CategoryName(rs.getString("name")),
rs.getString("description")
);
}
}

View file

@ -0,0 +1,253 @@
package de.effigenix.infrastructure.masterdata.persistence;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.*;
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.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
@Repository
@Profile("!no-db")
public class JdbcSupplierRepository implements SupplierRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcSupplierRepository.class);
private final JdbcClient jdbc;
public JdbcSupplierRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Override
public Result<RepositoryError, Optional<Supplier>> findById(SupplierId id) {
try {
var supplierOpt = jdbc.sql("SELECT * FROM suppliers WHERE id = :id")
.param("id", id.value())
.query(this::mapSupplierRow)
.optional();
if (supplierOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadCertificates(supplierOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Supplier>> findAll() {
try {
var suppliers = jdbc.sql("SELECT * FROM suppliers ORDER BY name")
.query(this::mapSupplierRow)
.list()
.stream()
.map(this::loadCertificates)
.toList();
return Result.success(suppliers);
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Supplier>> findByStatus(SupplierStatus status) {
try {
var suppliers = jdbc.sql("SELECT * FROM suppliers WHERE status = :status ORDER BY name")
.param("status", status.name())
.query(this::mapSupplierRow)
.list()
.stream()
.map(this::loadCertificates)
.toList();
return Result.success(suppliers);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> save(Supplier supplier) {
try {
int rows = jdbc.sql("""
UPDATE suppliers
SET name = :name, phone = :phone, email = :email, contact_person = :contactPerson,
street = :street, house_number = :houseNumber, postal_code = :postalCode,
city = :city, country = :country,
payment_due_days = :paymentDueDays, payment_description = :paymentDescription,
quality_score = :qualityScore, delivery_score = :deliveryScore, price_score = :priceScore,
status = :status, updated_at = :updatedAt
WHERE id = :id
""")
.param("id", supplier.id().value())
.params(supplierParams(supplier))
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO suppliers
(id, name, phone, email, contact_person, street, house_number, postal_code, city, country,
payment_due_days, payment_description, quality_score, delivery_score, price_score,
status, created_at, updated_at)
VALUES (:id, :name, :phone, :email, :contactPerson, :street, :houseNumber, :postalCode,
:city, :country, :paymentDueDays, :paymentDescription,
:qualityScore, :deliveryScore, :priceScore, :status, :createdAt, :updatedAt)
""")
.param("id", supplier.id().value())
.param("createdAt", supplier.createdAt())
.params(supplierParams(supplier))
.update();
}
saveCertificates(supplier);
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(Supplier supplier) {
try {
jdbc.sql("DELETE FROM suppliers WHERE id = :id")
.param("id", supplier.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(SupplierName name) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM suppliers 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()));
}
}
private java.util.Map<String, Object> supplierParams(Supplier supplier) {
var params = new java.util.LinkedHashMap<String, Object>();
params.put("name", supplier.name().value());
params.put("phone", supplier.contactInfo() != null ? supplier.contactInfo().phone() : null);
params.put("email", supplier.contactInfo() != null ? supplier.contactInfo().email() : null);
params.put("contactPerson", supplier.contactInfo() != null ? supplier.contactInfo().contactPerson() : null);
params.put("street", supplier.address() != null ? supplier.address().street() : null);
params.put("houseNumber", supplier.address() != null ? supplier.address().houseNumber() : null);
params.put("postalCode", supplier.address() != null ? supplier.address().postalCode() : null);
params.put("city", supplier.address() != null ? supplier.address().city() : null);
params.put("country", supplier.address() != null ? supplier.address().country() : null);
params.put("paymentDueDays", supplier.paymentTerms() != null ? supplier.paymentTerms().paymentDueDays() : null);
params.put("paymentDescription", supplier.paymentTerms() != null ? supplier.paymentTerms().description() : null);
params.put("qualityScore", supplier.rating() != null ? supplier.rating().qualityScore() : null);
params.put("deliveryScore", supplier.rating() != null ? supplier.rating().deliveryScore() : null);
params.put("priceScore", supplier.rating() != null ? supplier.rating().priceScore() : null);
params.put("status", supplier.status().name());
params.put("updatedAt", supplier.updatedAt());
return params;
}
private void saveCertificates(Supplier supplier) {
jdbc.sql("DELETE FROM quality_certificates WHERE supplier_id = :supplierId")
.param("supplierId", supplier.id().value())
.update();
for (QualityCertificate cert : supplier.certificates()) {
jdbc.sql("""
INSERT INTO quality_certificates (supplier_id, certificate_type, issuer, valid_from, valid_until)
VALUES (:supplierId, :certificateType, :issuer, :validFrom, :validUntil)
""")
.param("supplierId", supplier.id().value())
.param("certificateType", cert.certificateType())
.param("issuer", cert.issuer())
.param("validFrom", cert.validFrom())
.param("validUntil", cert.validUntil())
.update();
}
}
private Supplier loadCertificates(Supplier supplier) {
var certificates = jdbc.sql(
"SELECT * FROM quality_certificates WHERE supplier_id = :supplierId")
.param("supplierId", supplier.id().value())
.query(this::mapCertificateRow)
.list();
return Supplier.reconstitute(
supplier.id(), supplier.name(), supplier.address(), supplier.contactInfo(),
supplier.paymentTerms(), certificates, supplier.rating(), supplier.status(),
supplier.createdAt(), supplier.updatedAt()
);
}
private Supplier mapSupplierRow(ResultSet rs, int rowNum) throws SQLException {
// Address (optional)
Address address = null;
String street = rs.getString("street");
if (street != null) {
address = new Address(street, rs.getString("house_number"),
rs.getString("postal_code"), rs.getString("city"), rs.getString("country"));
}
// ContactInfo
ContactInfo contactInfo = new ContactInfo(
rs.getString("phone"), rs.getString("email"), rs.getString("contact_person"));
// PaymentTerms (optional)
PaymentTerms paymentTerms = null;
Integer dueDays = rs.getObject("payment_due_days", Integer.class);
if (dueDays != null) {
paymentTerms = new PaymentTerms(dueDays, rs.getString("payment_description"));
}
// SupplierRating (optional)
SupplierRating rating = null;
Integer qualityScore = rs.getObject("quality_score", Integer.class);
if (qualityScore != null) {
rating = new SupplierRating(qualityScore,
rs.getInt("delivery_score"), rs.getInt("price_score"));
}
return Supplier.reconstitute(
SupplierId.of(rs.getString("id")),
new SupplierName(rs.getString("name")),
address,
contactInfo,
paymentTerms,
List.of(),
rating,
SupplierStatus.valueOf(rs.getString("status")),
rs.getObject("created_at", OffsetDateTime.class),
rs.getObject("updated_at", OffsetDateTime.class)
);
}
private QualityCertificate mapCertificateRow(ResultSet rs, int rowNum) throws SQLException {
return new QualityCertificate(
rs.getString("certificate_type"),
rs.getString("issuer"),
rs.getObject("valid_from", LocalDate.class),
rs.getObject("valid_until", LocalDate.class)
);
}
}

View file

@ -1,77 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Entity
@Table(name = "articles")
public class ArticleEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Column(name = "name", nullable = false, length = 200)
private String name;
@Column(name = "article_number", nullable = false, unique = true, length = 50)
private String articleNumber;
@Column(name = "category_id", nullable = false, length = 36)
private String categoryId;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "created_at", nullable = false, updatable = false)
private OffsetDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<SalesUnitEntity> salesUnits = new ArrayList<>();
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "article_suppliers", joinColumns = @JoinColumn(name = "article_id"))
@Column(name = "supplier_id")
private Set<String> supplierIds = new HashSet<>();
protected ArticleEntity() {}
public ArticleEntity(String id, String name, String articleNumber, String categoryId,
String status, OffsetDateTime createdAt, OffsetDateTime updatedAt) {
this.id = id;
this.name = name;
this.articleNumber = articleNumber;
this.categoryId = categoryId;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public String getId() { return id; }
public String getName() { return name; }
public String getArticleNumber() { return articleNumber; }
public String getCategoryId() { return categoryId; }
public String getStatus() { return status; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public OffsetDateTime getUpdatedAt() { return updatedAt; }
public List<SalesUnitEntity> getSalesUnits() { return salesUnits; }
public Set<String> getSupplierIds() { return supplierIds; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setArticleNumber(String articleNumber) { this.articleNumber = articleNumber; }
public void setCategoryId(String categoryId) { this.categoryId = categoryId; }
public void setStatus(String status) { this.status = status; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
public void setSalesUnits(List<SalesUnitEntity> salesUnits) { this.salesUnits = salesUnits; }
public void setSupplierIds(Set<String> supplierIds) { this.supplierIds = supplierIds; }
}

View file

@ -1,42 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.math.BigDecimal;
@Embeddable
public class ContractLineItemEmbeddable {
@Column(name = "article_id", nullable = false, length = 36)
private String articleId;
@Column(name = "agreed_price_amount", nullable = false, precision = 19, scale = 2)
private BigDecimal agreedPriceAmount;
@Column(name = "agreed_price_currency", nullable = false, length = 3)
private String agreedPriceCurrency;
@Column(name = "agreed_quantity", precision = 19, scale = 4)
private BigDecimal agreedQuantity;
@Column(name = "unit", length = 30)
private String unit;
protected ContractLineItemEmbeddable() {}
public ContractLineItemEmbeddable(String articleId, BigDecimal agreedPriceAmount,
String agreedPriceCurrency, BigDecimal agreedQuantity, String unit) {
this.articleId = articleId;
this.agreedPriceAmount = agreedPriceAmount;
this.agreedPriceCurrency = agreedPriceCurrency;
this.agreedQuantity = agreedQuantity;
this.unit = unit;
}
public String getArticleId() { return articleId; }
public BigDecimal getAgreedPriceAmount() { return agreedPriceAmount; }
public String getAgreedPriceCurrency() { return agreedPriceCurrency; }
public BigDecimal getAgreedQuantity() { return agreedQuantity; }
public String getUnit() { return unit; }
}

View file

@ -1,119 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Entity
@Table(name = "customers")
public class CustomerEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Column(name = "name", nullable = false, unique = true, length = 200)
private String name;
@Column(name = "type", nullable = false, length = 10)
private String type;
@Column(name = "phone", length = 50)
private String phone;
@Column(name = "email", length = 255)
private String email;
@Column(name = "contact_person", length = 200)
private String contactPerson;
@Column(name = "billing_street", nullable = false, length = 200)
private String billingStreet;
@Column(name = "billing_house_number", length = 20)
private String billingHouseNumber;
@Column(name = "billing_postal_code", nullable = false, length = 20)
private String billingPostalCode;
@Column(name = "billing_city", nullable = false, length = 100)
private String billingCity;
@Column(name = "billing_country", nullable = false, length = 2)
private String billingCountry;
@Column(name = "payment_due_days")
private Integer paymentDueDays;
@Column(name = "payment_description", length = 500)
private String paymentDescription;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "created_at", nullable = false, updatable = false)
private OffsetDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "delivery_addresses", joinColumns = @JoinColumn(name = "customer_id"))
private List<DeliveryAddressEmbeddable> deliveryAddresses = new ArrayList<>();
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "customer_preferences", joinColumns = @JoinColumn(name = "customer_id"))
@Column(name = "preference")
@Enumerated(EnumType.STRING)
private Set<de.effigenix.domain.masterdata.CustomerPreference> preferences = new HashSet<>();
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "id", referencedColumnName = "customer_id", insertable = false, updatable = false)
private FrameContractEntity frameContract;
public CustomerEntity() {}
public String getId() { return id; }
public String getName() { return name; }
public String getType() { return type; }
public String getPhone() { return phone; }
public String getEmail() { return email; }
public String getContactPerson() { return contactPerson; }
public String getBillingStreet() { return billingStreet; }
public String getBillingHouseNumber() { return billingHouseNumber; }
public String getBillingPostalCode() { return billingPostalCode; }
public String getBillingCity() { return billingCity; }
public String getBillingCountry() { return billingCountry; }
public Integer getPaymentDueDays() { return paymentDueDays; }
public String getPaymentDescription() { return paymentDescription; }
public String getStatus() { return status; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public OffsetDateTime getUpdatedAt() { return updatedAt; }
public List<DeliveryAddressEmbeddable> getDeliveryAddresses() { return deliveryAddresses; }
public Set<de.effigenix.domain.masterdata.CustomerPreference> getPreferences() { return preferences; }
public FrameContractEntity getFrameContract() { return frameContract; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setType(String type) { this.type = type; }
public void setPhone(String phone) { this.phone = phone; }
public void setEmail(String email) { this.email = email; }
public void setContactPerson(String contactPerson) { this.contactPerson = contactPerson; }
public void setBillingStreet(String billingStreet) { this.billingStreet = billingStreet; }
public void setBillingHouseNumber(String billingHouseNumber) { this.billingHouseNumber = billingHouseNumber; }
public void setBillingPostalCode(String billingPostalCode) { this.billingPostalCode = billingPostalCode; }
public void setBillingCity(String billingCity) { this.billingCity = billingCity; }
public void setBillingCountry(String billingCountry) { this.billingCountry = billingCountry; }
public void setPaymentDueDays(Integer paymentDueDays) { this.paymentDueDays = paymentDueDays; }
public void setPaymentDescription(String paymentDescription) { this.paymentDescription = paymentDescription; }
public void setStatus(String status) { this.status = status; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
public void setDeliveryAddresses(List<DeliveryAddressEmbeddable> deliveryAddresses) { this.deliveryAddresses = deliveryAddresses; }
public void setPreferences(Set<de.effigenix.domain.masterdata.CustomerPreference> preferences) { this.preferences = preferences; }
public void setFrameContract(FrameContractEntity frameContract) { this.frameContract = frameContract; }
}

View file

@ -1,56 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
@Embeddable
public class DeliveryAddressEmbeddable {
@Column(name = "label", length = 100)
private String label;
@Column(name = "street", nullable = false, length = 200)
private String street;
@Column(name = "house_number", length = 20)
private String houseNumber;
@Column(name = "postal_code", nullable = false, length = 20)
private String postalCode;
@Column(name = "city", nullable = false, length = 100)
private String city;
@Column(name = "country", nullable = false, length = 2)
private String country;
@Column(name = "contact_person", length = 200)
private String contactPerson;
@Column(name = "delivery_notes", length = 500)
private String deliveryNotes;
protected DeliveryAddressEmbeddable() {}
public DeliveryAddressEmbeddable(String label, String street, String houseNumber,
String postalCode, String city, String country,
String contactPerson, String deliveryNotes) {
this.label = label;
this.street = street;
this.houseNumber = houseNumber;
this.postalCode = postalCode;
this.city = city;
this.country = country;
this.contactPerson = contactPerson;
this.deliveryNotes = deliveryNotes;
}
public String getLabel() { return label; }
public String getStreet() { return street; }
public String getHouseNumber() { return houseNumber; }
public String getPostalCode() { return postalCode; }
public String getCity() { return city; }
public String getCountry() { return country; }
public String getContactPerson() { return contactPerson; }
public String getDeliveryNotes() { return deliveryNotes; }
}

View file

@ -1,57 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "frame_contracts")
public class FrameContractEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Column(name = "customer_id", nullable = false, unique = true, length = 36)
private String customerId;
@Column(name = "valid_from")
private LocalDate validFrom;
@Column(name = "valid_until")
private LocalDate validUntil;
@Column(name = "delivery_rhythm", nullable = false, length = 20)
private String deliveryRhythm;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "contract_line_items", joinColumns = @JoinColumn(name = "frame_contract_id"))
private List<ContractLineItemEmbeddable> lineItems = new ArrayList<>();
protected FrameContractEntity() {}
public FrameContractEntity(String id, String customerId, LocalDate validFrom,
LocalDate validUntil, String deliveryRhythm) {
this.id = id;
this.customerId = customerId;
this.validFrom = validFrom;
this.validUntil = validUntil;
this.deliveryRhythm = deliveryRhythm;
}
public String getId() { return id; }
public String getCustomerId() { return customerId; }
public LocalDate getValidFrom() { return validFrom; }
public LocalDate getValidUntil() { return validUntil; }
public String getDeliveryRhythm() { return deliveryRhythm; }
public List<ContractLineItemEmbeddable> getLineItems() { return lineItems; }
public void setId(String id) { this.id = id; }
public void setCustomerId(String customerId) { this.customerId = customerId; }
public void setValidFrom(LocalDate validFrom) { this.validFrom = validFrom; }
public void setValidUntil(LocalDate validUntil) { this.validUntil = validUntil; }
public void setDeliveryRhythm(String deliveryRhythm) { this.deliveryRhythm = deliveryRhythm; }
public void setLineItems(List<ContractLineItemEmbeddable> lineItems) { this.lineItems = lineItems; }
}

View file

@ -1,37 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "product_categories")
public class ProductCategoryEntity {
@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 = "description", columnDefinition = "TEXT")
private String description;
protected ProductCategoryEntity() {}
public ProductCategoryEntity(String id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
public String getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setDescription(String description) { this.description = description; }
}

View file

@ -1,41 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.time.LocalDate;
@Embeddable
public class QualityCertificateEmbeddable {
@Column(name = "certificate_type", nullable = false, length = 100)
private String certificateType;
@Column(name = "issuer", length = 200)
private String issuer;
@Column(name = "valid_from")
private LocalDate validFrom;
@Column(name = "valid_until")
private LocalDate validUntil;
protected QualityCertificateEmbeddable() {}
public QualityCertificateEmbeddable(String certificateType, String issuer, LocalDate validFrom, LocalDate validUntil) {
this.certificateType = certificateType;
this.issuer = issuer;
this.validFrom = validFrom;
this.validUntil = validUntil;
}
public String getCertificateType() { return certificateType; }
public String getIssuer() { return issuer; }
public LocalDate getValidFrom() { return validFrom; }
public LocalDate getValidUntil() { return validUntil; }
public void setCertificateType(String certificateType) { this.certificateType = certificateType; }
public void setIssuer(String issuer) { this.issuer = issuer; }
public void setValidFrom(LocalDate validFrom) { this.validFrom = validFrom; }
public void setValidUntil(LocalDate validUntil) { this.validUntil = validUntil; }
}

View file

@ -1,56 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "sales_units")
public class SalesUnitEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id", nullable = false)
private ArticleEntity article;
@Column(name = "unit", nullable = false, length = 30)
private String unit;
@Column(name = "price_model", nullable = false, length = 30)
private String priceModel;
@Column(name = "price_amount", nullable = false, precision = 19, scale = 2)
private BigDecimal priceAmount;
@Column(name = "price_currency", nullable = false, length = 3)
private String priceCurrency;
protected SalesUnitEntity() {}
public SalesUnitEntity(String id, ArticleEntity article, String unit, String priceModel,
BigDecimal priceAmount, String priceCurrency) {
this.id = id;
this.article = article;
this.unit = unit;
this.priceModel = priceModel;
this.priceAmount = priceAmount;
this.priceCurrency = priceCurrency;
}
public String getId() { return id; }
public ArticleEntity getArticle() { return article; }
public String getUnit() { return unit; }
public String getPriceModel() { return priceModel; }
public BigDecimal getPriceAmount() { return priceAmount; }
public String getPriceCurrency() { return priceCurrency; }
public void setId(String id) { this.id = id; }
public void setArticle(ArticleEntity article) { this.article = article; }
public void setUnit(String unit) { this.unit = unit; }
public void setPriceModel(String priceModel) { this.priceModel = priceModel; }
public void setPriceAmount(BigDecimal priceAmount) { this.priceAmount = priceAmount; }
public void setPriceCurrency(String priceCurrency) { this.priceCurrency = priceCurrency; }
}

View file

@ -1,113 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.entity;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "suppliers")
public class SupplierEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Column(name = "name", nullable = false, unique = true, length = 200)
private String name;
@Column(name = "phone", length = 50)
private String phone;
@Column(name = "email", length = 255)
private String email;
@Column(name = "contact_person", length = 200)
private String contactPerson;
@Column(name = "street", length = 200)
private String street;
@Column(name = "house_number", length = 20)
private String houseNumber;
@Column(name = "postal_code", length = 20)
private String postalCode;
@Column(name = "city", length = 100)
private String city;
@Column(name = "country", length = 2)
private String country;
@Column(name = "payment_due_days")
private Integer paymentDueDays;
@Column(name = "payment_description", length = 500)
private String paymentDescription;
@Column(name = "quality_score")
private Integer qualityScore;
@Column(name = "delivery_score")
private Integer deliveryScore;
@Column(name = "price_score")
private Integer priceScore;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "created_at", nullable = false, updatable = false)
private OffsetDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "quality_certificates", joinColumns = @JoinColumn(name = "supplier_id"))
private List<QualityCertificateEmbeddable> certificates = new ArrayList<>();
public SupplierEntity() {}
public String getId() { return id; }
public String getName() { return name; }
public String getPhone() { return phone; }
public String getEmail() { return email; }
public String getContactPerson() { return contactPerson; }
public String getStreet() { return street; }
public String getHouseNumber() { return houseNumber; }
public String getPostalCode() { return postalCode; }
public String getCity() { return city; }
public String getCountry() { return country; }
public Integer getPaymentDueDays() { return paymentDueDays; }
public String getPaymentDescription() { return paymentDescription; }
public Integer getQualityScore() { return qualityScore; }
public Integer getDeliveryScore() { return deliveryScore; }
public Integer getPriceScore() { return priceScore; }
public String getStatus() { return status; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public OffsetDateTime getUpdatedAt() { return updatedAt; }
public List<QualityCertificateEmbeddable> getCertificates() { return certificates; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setPhone(String phone) { this.phone = phone; }
public void setEmail(String email) { this.email = email; }
public void setContactPerson(String contactPerson) { this.contactPerson = contactPerson; }
public void setStreet(String street) { this.street = street; }
public void setHouseNumber(String houseNumber) { this.houseNumber = houseNumber; }
public void setPostalCode(String postalCode) { this.postalCode = postalCode; }
public void setCity(String city) { this.city = city; }
public void setCountry(String country) { this.country = country; }
public void setPaymentDueDays(Integer paymentDueDays) { this.paymentDueDays = paymentDueDays; }
public void setPaymentDescription(String paymentDescription) { this.paymentDescription = paymentDescription; }
public void setQualityScore(Integer qualityScore) { this.qualityScore = qualityScore; }
public void setDeliveryScore(Integer deliveryScore) { this.deliveryScore = deliveryScore; }
public void setPriceScore(Integer priceScore) { this.priceScore = priceScore; }
public void setStatus(String status) { this.status = status; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
public void setCertificates(List<QualityCertificateEmbeddable> certificates) { this.certificates = certificates; }
}

View file

@ -1,83 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.mapper;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.entity.ArticleEntity;
import de.effigenix.infrastructure.masterdata.persistence.entity.SalesUnitEntity;
import de.effigenix.shared.common.Money;
import org.springframework.stereotype.Component;
import java.util.Currency;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class ArticleMapper {
public ArticleEntity toEntity(Article article) {
var entity = new ArticleEntity(
article.id().value(),
article.name().value(),
article.articleNumber().value(),
article.categoryId().value(),
article.status().name(),
article.createdAt(),
article.updatedAt()
);
List<SalesUnitEntity> salesUnitEntities = article.salesUnits().stream()
.map(su -> toSalesUnitEntity(su, entity))
.collect(Collectors.toList());
entity.setSalesUnits(salesUnitEntities);
Set<String> supplierIds = article.supplierReferences().stream()
.map(SupplierId::value)
.collect(Collectors.toSet());
entity.setSupplierIds(supplierIds);
return entity;
}
public Article toDomain(ArticleEntity entity) {
List<SalesUnit> salesUnits = entity.getSalesUnits().stream()
.map(this::toDomainSalesUnit)
.collect(Collectors.toList());
Set<SupplierId> supplierRefs = entity.getSupplierIds().stream()
.map(SupplierId::of)
.collect(Collectors.toSet());
return Article.reconstitute(
ArticleId.of(entity.getId()),
new ArticleName(entity.getName()),
new ArticleNumber(entity.getArticleNumber()),
ProductCategoryId.of(entity.getCategoryId()),
salesUnits,
ArticleStatus.valueOf(entity.getStatus()),
supplierRefs,
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
private SalesUnitEntity toSalesUnitEntity(SalesUnit su, ArticleEntity article) {
return new SalesUnitEntity(
su.id().value(),
article,
su.unit().name(),
su.priceModel().name(),
su.price().amount(),
su.price().currency().getCurrencyCode()
);
}
private SalesUnit toDomainSalesUnit(SalesUnitEntity entity) {
return SalesUnit.reconstitute(
SalesUnitId.of(entity.getId()),
Unit.valueOf(entity.getUnit()),
PriceModel.valueOf(entity.getPriceModel()),
new Money(entity.getPriceAmount(), Currency.getInstance(entity.getPriceCurrency()))
);
}
}

View file

@ -1,138 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.mapper;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.entity.*;
import de.effigenix.shared.common.Address;
import de.effigenix.shared.common.ContactInfo;
import de.effigenix.shared.common.Money;
import de.effigenix.shared.common.PaymentTerms;
import org.springframework.stereotype.Component;
import java.util.Currency;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class CustomerMapper {
public CustomerEntity toEntity(Customer customer) {
var entity = new CustomerEntity();
entity.setId(customer.id().value());
entity.setName(customer.name().value());
entity.setType(customer.type().name());
var contact = customer.contactInfo();
if (contact != null) {
entity.setPhone(contact.phone());
entity.setEmail(contact.email());
entity.setContactPerson(contact.contactPerson());
}
var billing = customer.billingAddress();
entity.setBillingStreet(billing.street());
entity.setBillingHouseNumber(billing.houseNumber());
entity.setBillingPostalCode(billing.postalCode());
entity.setBillingCity(billing.city());
entity.setBillingCountry(billing.country());
var terms = customer.paymentTerms();
if (terms != null) {
entity.setPaymentDueDays(terms.paymentDueDays());
entity.setPaymentDescription(terms.description());
}
entity.setStatus(customer.status().name());
entity.setCreatedAt(customer.createdAt());
entity.setUpdatedAt(customer.updatedAt());
entity.setDeliveryAddresses(customer.deliveryAddresses().stream()
.map(da -> new DeliveryAddressEmbeddable(
da.label(),
da.address().street(), da.address().houseNumber(),
da.address().postalCode(), da.address().city(), da.address().country(),
da.contactPerson(), da.deliveryNotes()))
.collect(Collectors.toList()));
entity.setPreferences(customer.preferences());
// FrameContract is saved separately not via the @OneToOne mapping
// (handled in JpaCustomerRepository)
return entity;
}
public Customer toDomain(CustomerEntity entity, FrameContractEntity fcEntity) {
Address billingAddress = new Address(
entity.getBillingStreet(), entity.getBillingHouseNumber(),
entity.getBillingPostalCode(), entity.getBillingCity(), entity.getBillingCountry()
);
ContactInfo contactInfo = new ContactInfo(
entity.getPhone(), entity.getEmail(), entity.getContactPerson()
);
PaymentTerms paymentTerms = null;
if (entity.getPaymentDueDays() != null) {
paymentTerms = new PaymentTerms(entity.getPaymentDueDays(), entity.getPaymentDescription());
}
List<DeliveryAddress> deliveryAddresses = entity.getDeliveryAddresses().stream()
.map(da -> new DeliveryAddress(
da.getLabel(),
new Address(da.getStreet(), da.getHouseNumber(),
da.getPostalCode(), da.getCity(), da.getCountry()),
da.getContactPerson(), da.getDeliveryNotes()))
.collect(Collectors.toList());
FrameContract frameContract = null;
if (fcEntity != null) {
List<ContractLineItem> lineItems = fcEntity.getLineItems().stream()
.map(li -> new ContractLineItem(
ArticleId.of(li.getArticleId()),
new Money(li.getAgreedPriceAmount(), Currency.getInstance(li.getAgreedPriceCurrency())),
li.getAgreedQuantity(),
li.getUnit() != null ? Unit.valueOf(li.getUnit()) : null))
.collect(Collectors.toList());
frameContract = FrameContract.reconstitute(
FrameContractId.of(fcEntity.getId()),
fcEntity.getValidFrom(),
fcEntity.getValidUntil(),
DeliveryRhythm.valueOf(fcEntity.getDeliveryRhythm()),
lineItems
);
}
return Customer.reconstitute(
CustomerId.of(entity.getId()),
new CustomerName(entity.getName()),
CustomerType.valueOf(entity.getType()),
billingAddress,
contactInfo,
paymentTerms,
deliveryAddresses,
frameContract,
entity.getPreferences(),
CustomerStatus.valueOf(entity.getStatus()),
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
public FrameContractEntity toFrameContractEntity(FrameContract fc, String customerId) {
var entity = new FrameContractEntity(
fc.id().value(), customerId,
fc.validFrom(), fc.validUntil(),
fc.deliveryRhythm().name()
);
entity.setLineItems(fc.lineItems().stream()
.map(li -> new ContractLineItemEmbeddable(
li.articleId().value(),
li.agreedPrice().amount(),
li.agreedPrice().currency().getCurrencyCode(),
li.agreedQuantity(),
li.unit() != null ? li.unit().name() : null))
.collect(Collectors.toList()));
return entity;
}
}

View file

@ -1,27 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.mapper;
import de.effigenix.domain.masterdata.CategoryName;
import de.effigenix.domain.masterdata.ProductCategory;
import de.effigenix.domain.masterdata.ProductCategoryId;
import de.effigenix.infrastructure.masterdata.persistence.entity.ProductCategoryEntity;
import org.springframework.stereotype.Component;
@Component
public class ProductCategoryMapper {
public ProductCategoryEntity toEntity(ProductCategory category) {
return new ProductCategoryEntity(
category.id().value(),
category.name().value(),
category.description()
);
}
public ProductCategory toDomain(ProductCategoryEntity entity) {
return ProductCategory.reconstitute(
ProductCategoryId.of(entity.getId()),
new CategoryName(entity.getName()),
entity.getDescription()
);
}
}

View file

@ -1,107 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.mapper;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.entity.QualityCertificateEmbeddable;
import de.effigenix.infrastructure.masterdata.persistence.entity.SupplierEntity;
import de.effigenix.shared.common.Address;
import de.effigenix.shared.common.ContactInfo;
import de.effigenix.shared.common.PaymentTerms;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class SupplierMapper {
public SupplierEntity toEntity(Supplier supplier) {
var entity = new SupplierEntity();
entity.setId(supplier.id().value());
entity.setName(supplier.name().value());
var contact = supplier.contactInfo();
if (contact != null) {
entity.setPhone(contact.phone());
entity.setEmail(contact.email());
entity.setContactPerson(contact.contactPerson());
}
var address = supplier.address();
if (address != null) {
entity.setStreet(address.street());
entity.setHouseNumber(address.houseNumber());
entity.setPostalCode(address.postalCode());
entity.setCity(address.city());
entity.setCountry(address.country());
}
var terms = supplier.paymentTerms();
if (terms != null) {
entity.setPaymentDueDays(terms.paymentDueDays());
entity.setPaymentDescription(terms.description());
}
var rating = supplier.rating();
if (rating != null) {
entity.setQualityScore(rating.qualityScore());
entity.setDeliveryScore(rating.deliveryScore());
entity.setPriceScore(rating.priceScore());
}
entity.setStatus(supplier.status().name());
entity.setCreatedAt(supplier.createdAt());
entity.setUpdatedAt(supplier.updatedAt());
List<QualityCertificateEmbeddable> certs = supplier.certificates().stream()
.map(c -> new QualityCertificateEmbeddable(
c.certificateType(), c.issuer(), c.validFrom(), c.validUntil()))
.collect(Collectors.toList());
entity.setCertificates(certs);
return entity;
}
public Supplier toDomain(SupplierEntity entity) {
Address address = null;
if (entity.getStreet() != null) {
address = new Address(
entity.getStreet(), entity.getHouseNumber(),
entity.getPostalCode(), entity.getCity(), entity.getCountry()
);
}
ContactInfo contactInfo = new ContactInfo(
entity.getPhone(), entity.getEmail(), entity.getContactPerson()
);
PaymentTerms paymentTerms = null;
if (entity.getPaymentDueDays() != null) {
paymentTerms = new PaymentTerms(entity.getPaymentDueDays(), entity.getPaymentDescription());
}
SupplierRating rating = null;
if (entity.getQualityScore() != null) {
rating = new SupplierRating(
entity.getQualityScore(), entity.getDeliveryScore(), entity.getPriceScore()
);
}
List<QualityCertificate> certificates = entity.getCertificates().stream()
.map(c -> new QualityCertificate(
c.getCertificateType(), c.getIssuer(), c.getValidFrom(), c.getValidUntil()))
.collect(Collectors.toList());
return Supplier.reconstitute(
SupplierId.of(entity.getId()),
new SupplierName(entity.getName()),
address,
contactInfo,
paymentTerms,
certificates,
rating,
SupplierStatus.valueOf(entity.getStatus()),
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
}

View file

@ -1,15 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.infrastructure.masterdata.persistence.entity.ArticleEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ArticleJpaRepository extends JpaRepository<ArticleEntity, String> {
List<ArticleEntity> findByCategoryId(String categoryId);
List<ArticleEntity> findByStatus(String status);
boolean existsByArticleNumber(String articleNumber);
}

View file

@ -1,15 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.infrastructure.masterdata.persistence.entity.CustomerEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CustomerJpaRepository extends JpaRepository<CustomerEntity, String> {
List<CustomerEntity> findByType(String type);
List<CustomerEntity> findByStatus(String status);
boolean existsByName(String name);
}

View file

@ -1,16 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.infrastructure.masterdata.persistence.entity.FrameContractEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface FrameContractJpaRepository extends JpaRepository<FrameContractEntity, String> {
Optional<FrameContractEntity> findByCustomerId(String customerId);
List<FrameContractEntity> findByCustomerIdIn(List<String> customerIds);
void deleteByCustomerId(String customerId);
}

View file

@ -1,116 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.mapper.ArticleMapper;
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 JpaArticleRepository implements ArticleRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaArticleRepository.class);
private final ArticleJpaRepository jpaRepository;
private final ArticleMapper mapper;
public JpaArticleRepository(ArticleJpaRepository jpaRepository, ArticleMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public Result<RepositoryError, Optional<Article>> findById(ArticleId id) {
try {
Optional<Article> 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<Article>> findAll() {
try {
List<Article> 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<Article>> findByCategory(ProductCategoryId categoryId) {
try {
List<Article> result = jpaRepository.findByCategoryId(categoryId.value()).stream()
.map(mapper::toDomain)
.collect(Collectors.toList());
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByCategory", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Article>> findByStatus(ArticleStatus status) {
try {
List<Article> result = jpaRepository.findByStatus(status.name()).stream()
.map(mapper::toDomain)
.collect(Collectors.toList());
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> save(Article article) {
try {
jpaRepository.save(mapper.toEntity(article));
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(Article article) {
try {
jpaRepository.deleteById(article.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByArticleNumber(ArticleNumber articleNumber) {
try {
return Result.success(jpaRepository.existsByArticleNumber(articleNumber.value()));
} catch (Exception e) {
logger.trace("Database error in existsByArticleNumber", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
}

View file

@ -1,138 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.entity.CustomerEntity;
import de.effigenix.infrastructure.masterdata.persistence.entity.FrameContractEntity;
import de.effigenix.infrastructure.masterdata.persistence.mapper.CustomerMapper;
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.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@Repository
@Profile("!no-db")
@Transactional(readOnly = true)
public class JpaCustomerRepository implements CustomerRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaCustomerRepository.class);
private final CustomerJpaRepository jpaRepository;
private final FrameContractJpaRepository frameContractJpaRepository;
private final CustomerMapper mapper;
public JpaCustomerRepository(CustomerJpaRepository jpaRepository,
FrameContractJpaRepository frameContractJpaRepository,
CustomerMapper mapper) {
this.jpaRepository = jpaRepository;
this.frameContractJpaRepository = frameContractJpaRepository;
this.mapper = mapper;
}
@Override
public Result<RepositoryError, Optional<Customer>> findById(CustomerId id) {
try {
var entityOpt = jpaRepository.findById(id.value());
if (entityOpt.isEmpty()) {
return Result.success(Optional.empty());
}
var fcEntity = frameContractJpaRepository.findByCustomerId(id.value()).orElse(null);
return Result.success(Optional.of(mapper.toDomain(entityOpt.get(), fcEntity)));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findAll() {
try {
return Result.success(mapWithFrameContracts(jpaRepository.findAll()));
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findByType(CustomerType type) {
try {
return Result.success(mapWithFrameContracts(jpaRepository.findByType(type.name())));
} catch (Exception e) {
logger.trace("Database error in findByType", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Customer>> findByStatus(CustomerStatus status) {
try {
return Result.success(mapWithFrameContracts(jpaRepository.findByStatus(status.name())));
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
private List<Customer> mapWithFrameContracts(List<CustomerEntity> entities) {
List<String> customerIds = entities.stream()
.map(CustomerEntity::getId)
.toList();
Map<String, FrameContractEntity> fcMap = frameContractJpaRepository.findByCustomerIdIn(customerIds).stream()
.collect(Collectors.toMap(FrameContractEntity::getCustomerId, Function.identity()));
return entities.stream()
.map(entity -> mapper.toDomain(entity, fcMap.get(entity.getId())))
.collect(Collectors.toList());
}
@Override
@Transactional
public Result<RepositoryError, Void> save(Customer customer) {
try {
jpaRepository.save(mapper.toEntity(customer));
// Handle FrameContract separately
frameContractJpaRepository.deleteByCustomerId(customer.id().value());
if (customer.frameContract() != null) {
var fcEntity = mapper.toFrameContractEntity(customer.frameContract(), customer.id().value());
frameContractJpaRepository.save(fcEntity);
}
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(Customer customer) {
try {
frameContractJpaRepository.deleteByCustomerId(customer.id().value());
jpaRepository.deleteById(customer.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(CustomerName 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()));
}
}
}

View file

@ -1,93 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.domain.masterdata.CategoryName;
import de.effigenix.domain.masterdata.ProductCategory;
import de.effigenix.domain.masterdata.ProductCategoryId;
import de.effigenix.domain.masterdata.ProductCategoryRepository;
import de.effigenix.infrastructure.masterdata.persistence.mapper.ProductCategoryMapper;
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 JpaProductCategoryRepository implements ProductCategoryRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaProductCategoryRepository.class);
private final ProductCategoryJpaRepository jpaRepository;
private final ProductCategoryMapper mapper;
public JpaProductCategoryRepository(ProductCategoryJpaRepository jpaRepository, ProductCategoryMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public Result<RepositoryError, Optional<ProductCategory>> findById(ProductCategoryId id) {
try {
Optional<ProductCategory> 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<ProductCategory>> findAll() {
try {
List<ProductCategory> 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
@Transactional
public Result<RepositoryError, Void> save(ProductCategory category) {
try {
jpaRepository.save(mapper.toEntity(category));
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(ProductCategory category) {
try {
jpaRepository.deleteById(category.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(CategoryName 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()));
}
}
}

View file

@ -1,103 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.domain.masterdata.*;
import de.effigenix.infrastructure.masterdata.persistence.mapper.SupplierMapper;
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 JpaSupplierRepository implements SupplierRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaSupplierRepository.class);
private final SupplierJpaRepository jpaRepository;
private final SupplierMapper mapper;
public JpaSupplierRepository(SupplierJpaRepository jpaRepository, SupplierMapper mapper) {
this.jpaRepository = jpaRepository;
this.mapper = mapper;
}
@Override
public Result<RepositoryError, Optional<Supplier>> findById(SupplierId id) {
try {
Optional<Supplier> 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<Supplier>> findAll() {
try {
List<Supplier> 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<Supplier>> findByStatus(SupplierStatus status) {
try {
List<Supplier> result = jpaRepository.findByStatus(status.name()).stream()
.map(mapper::toDomain)
.collect(Collectors.toList());
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> save(Supplier supplier) {
try {
jpaRepository.save(mapper.toEntity(supplier));
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(Supplier supplier) {
try {
jpaRepository.deleteById(supplier.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(SupplierName 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()));
}
}
}

View file

@ -1,13 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.infrastructure.masterdata.persistence.entity.ProductCategoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ProductCategoryJpaRepository extends JpaRepository<ProductCategoryEntity, String> {
boolean existsByName(String name);
Optional<ProductCategoryEntity> findByName(String name);
}

View file

@ -1,13 +0,0 @@
package de.effigenix.infrastructure.masterdata.persistence.repository;
import de.effigenix.infrastructure.masterdata.persistence.entity.SupplierEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface SupplierJpaRepository extends JpaRepository<SupplierEntity, String> {
List<SupplierEntity> findByStatus(String status);
boolean existsByName(String name);
}

View file

@ -0,0 +1,166 @@
package de.effigenix.infrastructure.usermanagement.persistence;
import de.effigenix.domain.usermanagement.*;
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.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
@Repository
@Profile("!no-db")
public class JdbcRoleRepository implements RoleRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcRoleRepository.class);
private final JdbcClient jdbc;
public JdbcRoleRepository(JdbcClient jdbc) {
this.jdbc = jdbc;
}
@Override
public Result<RepositoryError, Optional<Role>> findById(RoleId id) {
try {
var roleOpt = jdbc.sql("SELECT * FROM roles WHERE id = :id")
.param("id", id.value())
.query(this::mapRoleRow)
.optional();
if (roleOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadPermissions(roleOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Optional<Role>> findByName(RoleName name) {
try {
var roleOpt = jdbc.sql("SELECT * FROM roles WHERE name = :name")
.param("name", name.name())
.query(this::mapRoleRow)
.optional();
if (roleOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadPermissions(roleOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findByName", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Role>> findAll() {
try {
var roles = jdbc.sql("SELECT * FROM roles ORDER BY name")
.query(this::mapRoleRow)
.list()
.stream()
.map(this::loadPermissions)
.toList();
return Result.success(roles);
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> save(Role role) {
try {
int rows = jdbc.sql("""
UPDATE roles SET name = :name, description = :description
WHERE id = :id
""")
.param("id", role.id().value())
.param("name", role.name().name())
.param("description", role.description())
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO roles (id, name, description)
VALUES (:id, :name, :description)
""")
.param("id", role.id().value())
.param("name", role.name().name())
.param("description", role.description())
.update();
}
savePermissions(role);
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(Role role) {
try {
jdbc.sql("DELETE FROM roles WHERE id = :id")
.param("id", role.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(RoleName name) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM roles WHERE name = :name")
.param("name", name.name())
.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()));
}
}
private void savePermissions(Role role) {
jdbc.sql("DELETE FROM role_permissions WHERE role_id = :roleId")
.param("roleId", role.id().value())
.update();
for (Permission permission : role.permissions()) {
jdbc.sql("INSERT INTO role_permissions (role_id, permission) VALUES (:roleId, :permission)")
.param("roleId", role.id().value())
.param("permission", permission.name())
.update();
}
}
private Role loadPermissions(Role role) {
var permissions = jdbc.sql("SELECT permission FROM role_permissions WHERE role_id = :roleId")
.param("roleId", role.id().value())
.query((rs, rowNum) -> Permission.valueOf(rs.getString("permission")))
.set();
return Role.reconstitute(role.id(), role.name(), permissions, role.description());
}
private Role mapRoleRow(ResultSet rs, int rowNum) throws SQLException {
return Role.reconstitute(
RoleId.of(rs.getString("id")),
RoleName.valueOf(rs.getString("name")),
Set.of(),
rs.getString("description")
);
}
}

View file

@ -0,0 +1,263 @@
package de.effigenix.infrastructure.usermanagement.persistence;
import de.effigenix.domain.usermanagement.*;
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.sql.ResultSet;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.*;
@Repository
@Profile("!no-db")
public class JdbcUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcUserRepository.class);
private final JdbcClient jdbc;
private final RoleRepository roleRepository;
public JdbcUserRepository(JdbcClient jdbc, RoleRepository roleRepository) {
this.jdbc = jdbc;
this.roleRepository = roleRepository;
}
@Override
public Result<RepositoryError, Optional<User>> findById(UserId id) {
try {
var userOpt = jdbc.sql("SELECT * FROM users WHERE id = :id")
.param("id", id.value())
.query(this::mapUserRow)
.optional();
if (userOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadRoles(userOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findById", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Optional<User>> findByUsername(String username) {
try {
var userOpt = jdbc.sql("SELECT * FROM users WHERE username = :username")
.param("username", username)
.query(this::mapUserRow)
.optional();
if (userOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadRoles(userOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findByUsername", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Optional<User>> findByEmail(String email) {
try {
var userOpt = jdbc.sql("SELECT * FROM users WHERE email = :email")
.param("email", email)
.query(this::mapUserRow)
.optional();
if (userOpt.isEmpty()) {
return Result.success(Optional.empty());
}
return Result.success(Optional.of(loadRoles(userOpt.get())));
} catch (Exception e) {
logger.trace("Database error in findByEmail", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findByBranchId(String branchId) {
try {
var users = jdbc.sql("SELECT * FROM users WHERE branch_id = :branchId")
.param("branchId", branchId)
.query(this::mapUserRow)
.list()
.stream()
.map(this::loadRoles)
.toList();
return Result.success(users);
} catch (Exception e) {
logger.trace("Database error in findByBranchId", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findByStatus(UserStatus status) {
try {
var users = jdbc.sql("SELECT * FROM users WHERE status = :status")
.param("status", status.name())
.query(this::mapUserRow)
.list()
.stream()
.map(this::loadRoles)
.toList();
return Result.success(users);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findAll() {
try {
var users = jdbc.sql("SELECT * FROM users ORDER BY username")
.query(this::mapUserRow)
.list()
.stream()
.map(this::loadRoles)
.toList();
return Result.success(users);
} catch (Exception e) {
logger.trace("Database error in findAll", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> save(User user) {
try {
int rows = jdbc.sql("""
UPDATE users
SET username = :username, email = :email, password_hash = :passwordHash,
branch_id = :branchId, status = :status, last_login = :lastLogin
WHERE id = :id
""")
.param("id", user.id().value())
.param("username", user.username())
.param("email", user.email())
.param("passwordHash", user.passwordHash().value())
.param("branchId", user.branchId())
.param("status", user.status().name())
.param("lastLogin", user.lastLogin())
.update();
if (rows == 0) {
jdbc.sql("""
INSERT INTO users (id, username, email, password_hash, branch_id, status, created_at, last_login)
VALUES (:id, :username, :email, :passwordHash, :branchId, :status, :createdAt, :lastLogin)
""")
.param("id", user.id().value())
.param("username", user.username())
.param("email", user.email())
.param("passwordHash", user.passwordHash().value())
.param("branchId", user.branchId())
.param("status", user.status().name())
.param("createdAt", user.createdAt())
.param("lastLogin", user.lastLogin())
.update();
}
saveUserRoles(user);
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Void> delete(User user) {
try {
jdbc.sql("DELETE FROM users WHERE id = :id")
.param("id", user.id().value())
.update();
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByUsername(String username) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM users WHERE username = :username")
.param("username", username)
.query(Integer.class)
.single();
return Result.success(count > 0);
} catch (Exception e) {
logger.trace("Database error in existsByUsername", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByEmail(String email) {
try {
int count = jdbc.sql("SELECT COUNT(*) FROM users WHERE email = :email")
.param("email", email)
.query(Integer.class)
.single();
return Result.success(count > 0);
} catch (Exception e) {
logger.trace("Database error in existsByEmail", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
private void saveUserRoles(User user) {
jdbc.sql("DELETE FROM user_roles WHERE user_id = :userId")
.param("userId", user.id().value())
.update();
for (Role role : user.roles()) {
jdbc.sql("INSERT INTO user_roles (user_id, role_id) VALUES (:userId, :roleId)")
.param("userId", user.id().value())
.param("roleId", role.id().value())
.update();
}
}
private User loadRoles(User user) {
var roleIds = jdbc.sql("SELECT role_id FROM user_roles WHERE user_id = :userId")
.param("userId", user.id().value())
.query((rs, rowNum) -> rs.getString("role_id"))
.list();
Set<Role> roles = new HashSet<>();
for (String roleId : roleIds) {
switch (roleRepository.findById(RoleId.of(roleId))) {
case Result.Success(var opt) -> opt.ifPresent(roles::add);
case Result.Failure(var err) ->
logger.trace("Failed to load role {}: {}", roleId, err.message());
}
}
return User.reconstitute(
user.id(), user.username(), user.email(), user.passwordHash(),
roles, user.branchId(), user.status(), user.createdAt(), user.lastLogin()
);
}
private User mapUserRow(ResultSet rs, int rowNum) throws SQLException {
return User.reconstitute(
UserId.of(rs.getString("id")),
rs.getString("username"),
rs.getString("email"),
PasswordHash.of(rs.getString("password_hash")),
Set.of(),
rs.getString("branch_id"),
UserStatus.valueOf(rs.getString("status")),
rs.getObject("created_at", OffsetDateTime.class),
rs.getObject("last_login", OffsetDateTime.class)
);
}
}

View file

@ -1,83 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.entity;
import de.effigenix.domain.usermanagement.Permission;
import de.effigenix.domain.usermanagement.RoleName;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
/**
* JPA Entity for Role.
* Infrastructure layer - NOT part of domain model!
*/
@Entity
@Table(name = "roles")
public class RoleEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Enumerated(EnumType.STRING)
@Column(name = "name", nullable = false, unique = true, length = 50)
private RoleName name;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "role_permissions", joinColumns = @JoinColumn(name = "role_id"))
@Enumerated(EnumType.STRING)
@Column(name = "permission", nullable = false, length = 100)
private Set<Permission> permissions = new HashSet<>();
@Column(name = "description", length = 500)
private String description;
// JPA requires no-arg constructor
protected RoleEntity() {
}
public RoleEntity(
String id,
RoleName name,
Set<Permission> permissions,
String description
) {
this.id = id;
this.name = name;
this.permissions = permissions != null ? permissions : new HashSet<>();
this.description = description;
}
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public RoleName getName() {
return name;
}
public void setName(RoleName name) {
this.name = name;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View file

@ -1,154 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.entity;
import de.effigenix.domain.usermanagement.UserStatus;
import jakarta.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Set;
/**
* JPA Entity for User.
* Infrastructure layer - NOT part of domain model!
*/
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class UserEntity {
@Id
@Column(name = "id", nullable = false, length = 36)
private String id;
@Column(name = "username", nullable = false, unique = true, length = 100)
private String username;
@Column(name = "email", nullable = false, unique = true, length = 255)
private String email;
@Column(name = "password_hash", nullable = false, length = 60)
private String passwordHash;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<RoleEntity> roles = new HashSet<>();
@Column(name = "branch_id", length = 36)
private String branchId;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
private UserStatus status;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private OffsetDateTime createdAt;
@Column(name = "last_login")
private OffsetDateTime lastLogin;
// JPA requires no-arg constructor
protected UserEntity() {
}
public UserEntity(
String id,
String username,
String email,
String passwordHash,
Set<RoleEntity> roles,
String branchId,
UserStatus status,
OffsetDateTime createdAt,
OffsetDateTime lastLogin
) {
this.id = id;
this.username = username;
this.email = email;
this.passwordHash = passwordHash;
this.roles = roles != null ? roles : new HashSet<>();
this.branchId = branchId;
this.status = status;
this.createdAt = createdAt;
this.lastLogin = lastLogin;
}
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public Set<RoleEntity> getRoles() {
return roles;
}
public void setRoles(Set<RoleEntity> roles) {
this.roles = roles;
}
public String getBranchId() {
return branchId;
}
public void setBranchId(String branchId) {
this.branchId = branchId;
}
public UserStatus getStatus() {
return status;
}
public void setStatus(UserStatus status) {
this.status = status;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getLastLogin() {
return lastLogin;
}
public void setLastLogin(OffsetDateTime lastLogin) {
this.lastLogin = lastLogin;
}
}

View file

@ -1,55 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.mapper;
import de.effigenix.domain.usermanagement.Role;
import de.effigenix.domain.usermanagement.RoleId;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import org.springframework.stereotype.Component;
import java.util.HashSet;
/**
* Maps between Role domain entity and RoleEntity JPA entity.
* Infrastructure Layer - translates between Domain and Persistence layers.
*
* This is a crucial part of Hexagonal Architecture:
* - Domain layer defines pure business logic (Role)
* - Infrastructure layer handles persistence (RoleEntity)
* - Mapper translates between the two layers
*/
@Component
public class RoleMapper {
/**
* Converts a Role domain entity to a RoleEntity JPA entity.
* Used when saving to the database.
*/
public RoleEntity toEntity(Role role) {
if (role == null) {
return null;
}
return new RoleEntity(
role.id().value(),
role.name(),
new HashSet<>(role.permissions()),
role.description()
);
}
/**
* Converts a RoleEntity JPA entity to a Role domain entity.
* Used when loading from the database.
*/
public Role toDomain(RoleEntity entity) {
if (entity == null) {
return null;
}
return Role.reconstitute(
RoleId.of(entity.getId()),
entity.getName(),
new HashSet<>(entity.getPermissions()),
entity.getDescription()
);
}
}

View file

@ -1,83 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.mapper;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Maps between User domain entity and UserEntity JPA entity.
* Infrastructure Layer - translates between Domain and Persistence layers.
*
* This is a crucial part of Hexagonal Architecture:
* - Domain layer defines pure business logic (User)
* - Infrastructure layer handles persistence (UserEntity)
* - Mapper translates between the two layers
*/
@Component
public class UserMapper {
private final RoleMapper roleMapper;
public UserMapper(RoleMapper roleMapper) {
this.roleMapper = roleMapper;
}
/**
* Converts a User domain entity to a UserEntity JPA entity.
* Used when saving to the database.
*/
public UserEntity toEntity(User user) {
if (user == null) {
return null;
}
Set<RoleEntity> roleEntities = user.roles().stream()
.map(roleMapper::toEntity)
.collect(Collectors.toSet());
return new UserEntity(
user.id().value(),
user.username(),
user.email(),
user.passwordHash().value(),
roleEntities,
user.branchId(),
user.status(),
user.createdAt(),
user.lastLogin()
);
}
/**
* Converts a UserEntity JPA entity to a User domain entity.
* Used when loading from the database.
*/
public User toDomain(UserEntity entity) {
if (entity == null) {
return null;
}
Set<Role> roles = entity.getRoles() != null
? entity.getRoles().stream()
.map(roleMapper::toDomain)
.collect(Collectors.toSet())
: new HashSet<>();
return User.reconstitute(
UserId.of(entity.getId()),
entity.getUsername(),
entity.getEmail(),
PasswordHash.of(entity.getPasswordHash()),
roles,
entity.getBranchId(),
entity.getStatus(),
entity.getCreatedAt(),
entity.getLastLogin()
);
}
}

View file

@ -1,117 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.repository;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.domain.usermanagement.Role;
import de.effigenix.domain.usermanagement.RoleId;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.domain.usermanagement.RoleRepository;
import de.effigenix.infrastructure.usermanagement.persistence.mapper.RoleMapper;
import de.effigenix.shared.common.Result;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* JPA Adapter for RoleRepository (Domain Interface).
* Infrastructure Layer - implements the Domain's RoleRepository interface.
*
* This is the Adapter pattern in Hexagonal Architecture:
* - Domain defines the interface (RoleRepository)
* - Infrastructure implements it (JpaRoleRepository)
* - Uses Spring Data JPA (RoleJpaRepository) internally
* - Translates between Domain and JPA entities using RoleMapper
*
* @Transactional ensures database consistency.
*/
@Repository
@Profile("!no-db")
@Transactional(readOnly = true)
public class JpaRoleRepository implements RoleRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaRoleRepository.class);
private final RoleJpaRepository jpaRepository;
private final RoleMapper roleMapper;
public JpaRoleRepository(RoleJpaRepository jpaRepository, RoleMapper roleMapper) {
this.jpaRepository = jpaRepository;
this.roleMapper = roleMapper;
}
@Override
public Result<RepositoryError, Optional<Role>> findById(RoleId id) {
try {
Optional<Role> result = jpaRepository.findById(id.value())
.map(roleMapper::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<Role>> findByName(RoleName name) {
try {
Optional<Role> result = jpaRepository.findByName(name)
.map(roleMapper::toDomain);
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByName", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<Role>> findAll() {
try {
List<Role> result = jpaRepository.findAll().stream()
.map(roleMapper::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
@Transactional
public Result<RepositoryError, Void> save(Role role) {
try {
jpaRepository.save(roleMapper.toEntity(role));
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(Role role) {
try {
jpaRepository.deleteById(role.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByName(RoleName name) {
try {
return Result.success(jpaRepository.existsByName(name));
} catch (Exception e) {
logger.trace("Database error in existsByName", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
}

View file

@ -1,164 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.repository;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.domain.usermanagement.User;
import de.effigenix.domain.usermanagement.UserId;
import de.effigenix.domain.usermanagement.UserRepository;
import de.effigenix.domain.usermanagement.UserStatus;
import de.effigenix.infrastructure.usermanagement.persistence.mapper.UserMapper;
import de.effigenix.shared.common.Result;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* JPA Adapter for UserRepository (Domain Interface).
* Infrastructure Layer - implements the Domain's UserRepository interface.
*
* This is the Adapter pattern in Hexagonal Architecture:
* - Domain defines the interface (UserRepository)
* - Infrastructure implements it (JpaUserRepository)
* - Uses Spring Data JPA (UserJpaRepository) internally
* - Translates between Domain and JPA entities using UserMapper
*
* @Transactional ensures database consistency.
*/
@Repository
@Profile("!no-db")
@Transactional(readOnly = true)
public class JpaUserRepository implements UserRepository {
private static final Logger logger = LoggerFactory.getLogger(JpaUserRepository.class);
private final UserJpaRepository jpaRepository;
private final UserMapper userMapper;
public JpaUserRepository(UserJpaRepository jpaRepository, UserMapper userMapper) {
this.jpaRepository = jpaRepository;
this.userMapper = userMapper;
}
@Override
public Result<RepositoryError, Optional<User>> findById(UserId id) {
try {
Optional<User> result = jpaRepository.findById(id.value())
.map(userMapper::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<User>> findByUsername(String username) {
try {
Optional<User> result = jpaRepository.findByUsername(username)
.map(userMapper::toDomain);
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByUsername", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Optional<User>> findByEmail(String email) {
try {
Optional<User> result = jpaRepository.findByEmail(email)
.map(userMapper::toDomain);
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByEmail", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findByBranchId(String branchId) {
try {
List<User> result = jpaRepository.findByBranchId(branchId).stream()
.map(userMapper::toDomain)
.collect(Collectors.toList());
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByBranchId", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findByStatus(UserStatus status) {
try {
List<User> result = jpaRepository.findByStatus(status).stream()
.map(userMapper::toDomain)
.collect(Collectors.toList());
return Result.success(result);
} catch (Exception e) {
logger.trace("Database error in findByStatus", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, List<User>> findAll() {
try {
List<User> result = jpaRepository.findAll().stream()
.map(userMapper::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
@Transactional
public Result<RepositoryError, Void> save(User user) {
try {
jpaRepository.save(userMapper.toEntity(user));
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in save", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
@Transactional
public Result<RepositoryError, Void> delete(User user) {
try {
jpaRepository.deleteById(user.id().value());
return Result.success(null);
} catch (Exception e) {
logger.trace("Database error in delete", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByUsername(String username) {
try {
return Result.success(jpaRepository.existsByUsername(username));
} catch (Exception e) {
logger.trace("Database error in existsByUsername", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
@Override
public Result<RepositoryError, Boolean> existsByEmail(String email) {
try {
return Result.success(jpaRepository.existsByEmail(email));
} catch (Exception e) {
logger.trace("Database error in existsByEmail", e);
return Result.failure(new RepositoryError.DatabaseError(e.getMessage()));
}
}
}

View file

@ -1,28 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.repository;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* Spring Data JPA Repository for RoleEntity.
* Infrastructure Layer - automatically provides CRUD operations.
*
* Spring Data generates implementations at runtime based on method names.
*/
@Repository
public interface RoleJpaRepository extends JpaRepository<RoleEntity, String> {
/**
* Finds a role by its name.
*/
Optional<RoleEntity> findByName(RoleName name);
/**
* Checks if a role with the given name exists.
*/
boolean existsByName(RoleName name);
}

View file

@ -1,49 +0,0 @@
package de.effigenix.infrastructure.usermanagement.persistence.repository;
import de.effigenix.domain.usermanagement.UserStatus;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* Spring Data JPA Repository for UserEntity.
* Infrastructure Layer - automatically provides CRUD operations.
*
* Spring Data generates implementations at runtime based on method names.
*/
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, String> {
/**
* Finds a user by their username.
*/
Optional<UserEntity> findByUsername(String username);
/**
* Finds a user by their email.
*/
Optional<UserEntity> findByEmail(String email);
/**
* Finds all users assigned to a specific branch.
*/
List<UserEntity> findByBranchId(String branchId);
/**
* Finds all users with a specific status.
*/
List<UserEntity> findByStatus(UserStatus status);
/**
* Checks if a username already exists.
*/
boolean existsByUsername(String username);
/**
* Checks if an email already exists.
*/
boolean existsByEmail(String email);
}

View file

@ -5,6 +5,7 @@ import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.Money;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -18,6 +19,7 @@ import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ -28,6 +30,7 @@ import static org.mockito.Mockito.*;
class ArticleUseCaseTest {
@Mock private ArticleRepository articleRepository;
@Mock private UnitOfWork unitOfWork;
private final ActorId performedBy = ActorId.of("admin-user");
private final String CATEGORY_ID = UUID.randomUUID().toString();
@ -99,7 +102,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
createArticle = new CreateArticle(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
createArticle = new CreateArticle(articleRepository, unitOfWork);
}
@Test
@ -236,7 +241,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
updateArticle = new UpdateArticle(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
updateArticle = new UpdateArticle(articleRepository, unitOfWork);
}
@Test
@ -497,7 +504,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
activateArticle = new ActivateArticle(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
activateArticle = new ActivateArticle(articleRepository, unitOfWork);
}
@Test
@ -576,7 +585,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
deactivateArticle = new DeactivateArticle(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
deactivateArticle = new DeactivateArticle(articleRepository, unitOfWork);
}
@Test
@ -648,7 +659,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
addSalesUnit = new AddSalesUnit(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
addSalesUnit = new AddSalesUnit(articleRepository, unitOfWork);
}
@Test
@ -776,7 +789,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
removeSalesUnit = new RemoveSalesUnit(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
removeSalesUnit = new RemoveSalesUnit(articleRepository, unitOfWork);
}
@Test
@ -881,7 +896,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
updateSalesUnitPrice = new UpdateSalesUnitPrice(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
updateSalesUnitPrice = new UpdateSalesUnitPrice(articleRepository, unitOfWork);
}
@Test
@ -1004,7 +1021,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
assignSupplier = new AssignSupplier(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
assignSupplier = new AssignSupplier(articleRepository, unitOfWork);
}
@Test
@ -1082,7 +1101,9 @@ class ArticleUseCaseTest {
@BeforeEach
void setUp() {
removeSupplier = new RemoveSupplier(articleRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
removeSupplier = new RemoveSupplier(articleRepository, unitOfWork);
}
@Test

View file

@ -3,6 +3,7 @@ package de.effigenix.application.masterdata;
import de.effigenix.application.masterdata.command.*;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.*;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -19,6 +20,7 @@ import java.time.ZoneOffset;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ -29,11 +31,14 @@ import static org.mockito.Mockito.*;
class CustomerUseCaseTest {
@Mock private CustomerRepository customerRepository;
@Mock private UnitOfWork unitOfWork;
private ActorId performedBy;
@BeforeEach
void setUp() {
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>)inv.getArgument(0)).get());
performedBy = ActorId.of("admin-user");
}
@ -109,7 +114,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
createCustomer = new CreateCustomer(customerRepository);
createCustomer = new CreateCustomer(customerRepository, unitOfWork);
}
@Test
@ -215,7 +220,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
updateCustomer = new UpdateCustomer(customerRepository);
updateCustomer = new UpdateCustomer(customerRepository, unitOfWork);
}
@Test
@ -469,7 +474,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
activateCustomer = new ActivateCustomer(customerRepository);
activateCustomer = new ActivateCustomer(customerRepository, unitOfWork);
}
@Test
@ -540,7 +545,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
deactivateCustomer = new DeactivateCustomer(customerRepository);
deactivateCustomer = new DeactivateCustomer(customerRepository, unitOfWork);
}
@Test
@ -611,7 +616,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
addDeliveryAddress = new AddDeliveryAddress(customerRepository);
addDeliveryAddress = new AddDeliveryAddress(customerRepository, unitOfWork);
}
@Test
@ -712,7 +717,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
removeDeliveryAddress = new RemoveDeliveryAddress(customerRepository);
removeDeliveryAddress = new RemoveDeliveryAddress(customerRepository, unitOfWork);
}
@Test
@ -797,7 +802,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
setPreferences = new SetPreferences(customerRepository);
setPreferences = new SetPreferences(customerRepository, unitOfWork);
}
@Test
@ -887,7 +892,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
setFrameContract = new SetFrameContract(customerRepository);
setFrameContract = new SetFrameContract(customerRepository, unitOfWork);
}
private SetFrameContractCommand validFrameContractCommand(String customerId) {
@ -1049,7 +1054,7 @@ class CustomerUseCaseTest {
@BeforeEach
void setUp() {
removeFrameContract = new RemoveFrameContract(customerRepository);
removeFrameContract = new RemoveFrameContract(customerRepository, unitOfWork);
}
@Test

View file

@ -5,6 +5,7 @@ import de.effigenix.application.masterdata.command.UpdateProductCategoryCommand;
import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -27,11 +28,14 @@ class ProductCategoryUseCaseTest {
@Mock private ProductCategoryRepository categoryRepository;
@Mock private ArticleRepository articleRepository;
@Mock private UnitOfWork unitOfWork;
private ActorId performedBy;
@BeforeEach
void setUp() {
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
performedBy = ActorId.of("admin-user");
}
@ -59,7 +63,7 @@ class ProductCategoryUseCaseTest {
@BeforeEach
void setUp() {
useCase = new CreateProductCategory(categoryRepository);
useCase = new CreateProductCategory(categoryRepository, unitOfWork);
}
@Test
@ -186,7 +190,7 @@ class ProductCategoryUseCaseTest {
@BeforeEach
void setUp() {
useCase = new UpdateProductCategory(categoryRepository);
useCase = new UpdateProductCategory(categoryRepository, unitOfWork);
}
@Test
@ -378,7 +382,7 @@ class ProductCategoryUseCaseTest {
@BeforeEach
void setUp() {
useCase = new DeleteProductCategory(categoryRepository, articleRepository);
useCase = new DeleteProductCategory(categoryRepository, articleRepository, unitOfWork);
}
@Test

View file

@ -5,6 +5,7 @@ import de.effigenix.domain.masterdata.*;
import de.effigenix.shared.common.ContactInfo;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -29,6 +30,7 @@ import static org.mockito.Mockito.*;
class SupplierUseCaseTest {
@Mock private SupplierRepository supplierRepository;
@Mock private UnitOfWork unitOfWork;
private final ActorId performedBy = ActorId.of("admin-user");
@ -105,7 +107,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
createSupplier = new CreateSupplier(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
createSupplier = new CreateSupplier(supplierRepository, unitOfWork);
}
@Test
@ -214,7 +218,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
updateSupplier = new UpdateSupplier(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
updateSupplier = new UpdateSupplier(supplierRepository, unitOfWork);
}
@Test
@ -448,7 +454,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
activateSupplier = new ActivateSupplier(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
activateSupplier = new ActivateSupplier(supplierRepository, unitOfWork);
}
@Test
@ -519,7 +527,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
deactivateSupplier = new DeactivateSupplier(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
deactivateSupplier = new DeactivateSupplier(supplierRepository, unitOfWork);
}
@Test
@ -590,7 +600,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
rateSupplier = new RateSupplier(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
rateSupplier = new RateSupplier(supplierRepository, unitOfWork);
}
@Test
@ -699,7 +711,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
addCertificate = new AddCertificate(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
addCertificate = new AddCertificate(supplierRepository, unitOfWork);
}
@Test
@ -817,7 +831,9 @@ class SupplierUseCaseTest {
@BeforeEach
void setUp() {
removeCertificate = new RemoveCertificate(supplierRepository);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((java.util.function.Supplier<?>) inv.getArgument(0)).get());
removeCertificate = new RemoveCertificate(supplierRepository, unitOfWork);
}
@Test

View file

@ -5,6 +5,7 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -17,6 +18,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -30,6 +32,7 @@ class AssignRoleTest {
@Mock private RoleRepository roleRepository;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private AssignRole assignRole;
private ActorId performedBy;
@ -38,7 +41,9 @@ class AssignRoleTest {
@BeforeEach
void setUp() {
assignRole = new AssignRole(userRepository, roleRepository, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
assignRole = new AssignRole(userRepository, roleRepository, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
testUser = User.reconstitute(
UserId.of("user-1"), "john.doe", "john@example.com",

View file

@ -4,6 +4,7 @@ import de.effigenix.application.usermanagement.command.AuthenticateCommand;
import de.effigenix.application.usermanagement.dto.SessionToken;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -17,6 +18,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -30,6 +32,7 @@ class AuthenticateUserTest {
@Mock private PasswordHasher passwordHasher;
@Mock private SessionManager sessionManager;
@Mock private AuditLogger auditLogger;
@Mock private UnitOfWork unitOfWork;
@InjectMocks
private AuthenticateUser authenticateUser;
@ -41,6 +44,8 @@ class AuthenticateUserTest {
@BeforeEach
void setUp() {
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
validCommand = new AuthenticateCommand("john.doe", "Password123!");
validPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");

View file

@ -4,6 +4,7 @@ import de.effigenix.application.usermanagement.command.ChangePasswordCommand;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -16,6 +17,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -29,6 +31,7 @@ class ChangePasswordTest {
@Mock private PasswordHasher passwordHasher;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private ChangePassword changePassword;
private User testUser;
@ -39,7 +42,9 @@ class ChangePasswordTest {
@BeforeEach
void setUp() {
changePassword = new ChangePassword(userRepository, passwordHasher, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
changePassword = new ChangePassword(userRepository, passwordHasher, auditLogger, authPort, unitOfWork);
oldPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
newPasswordHash = new PasswordHash("$2b$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");

View file

@ -4,6 +4,7 @@ import de.effigenix.application.usermanagement.command.CreateUserCommand;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -15,6 +16,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@ -30,6 +32,7 @@ class CreateUserTest {
@Mock private PasswordHasher passwordHasher;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private CreateUser createUser;
private CreateUserCommand validCommand;
@ -38,7 +41,9 @@ class CreateUserTest {
@BeforeEach
void setUp() {
createUser = new CreateUser(userRepository, roleRepository, passwordHasher, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
createUser = new CreateUser(userRepository, roleRepository, passwordHasher, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
validPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
validCommand = new CreateUserCommand("john.doe", "john@example.com", "Password123!", Set.of(RoleName.PRODUCTION_WORKER), "branch-1");

View file

@ -5,6 +5,7 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -17,6 +18,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -29,6 +31,7 @@ class LockUserTest {
@Mock private UserRepository userRepository;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private LockUser lockUser;
private ActorId performedBy;
@ -36,7 +39,9 @@ class LockUserTest {
@BeforeEach
void setUp() {
lockUser = new LockUser(userRepository, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
lockUser = new LockUser(userRepository, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
activeUser = User.reconstitute(
UserId.of("user-1"), "john.doe", "john@example.com",

View file

@ -5,6 +5,7 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -18,6 +19,7 @@ import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -31,6 +33,7 @@ class RemoveRoleTest {
@Mock private RoleRepository roleRepository;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private RemoveRole removeRole;
private ActorId performedBy;
@ -39,7 +42,9 @@ class RemoveRoleTest {
@BeforeEach
void setUp() {
removeRole = new RemoveRole(userRepository, roleRepository, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
removeRole = new RemoveRole(userRepository, roleRepository, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Production Worker");
userWithRole = User.reconstitute(

View file

@ -5,6 +5,7 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -17,6 +18,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -29,6 +31,7 @@ class UnlockUserTest {
@Mock private UserRepository userRepository;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private UnlockUser unlockUser;
private ActorId performedBy;
@ -36,7 +39,9 @@ class UnlockUserTest {
@BeforeEach
void setUp() {
unlockUser = new UnlockUser(userRepository, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
unlockUser = new UnlockUser(userRepository, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
lockedUser = User.reconstitute(
UserId.of("user-1"), "john.doe", "john@example.com",

View file

@ -5,6 +5,7 @@ import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@ -17,6 +18,7 @@ import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Optional;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
@ -29,6 +31,7 @@ class UpdateUserTest {
@Mock private UserRepository userRepository;
@Mock private AuditLogger auditLogger;
@Mock private AuthorizationPort authPort;
@Mock private UnitOfWork unitOfWork;
private UpdateUser updateUser;
private ActorId performedBy;
@ -36,7 +39,9 @@ class UpdateUserTest {
@BeforeEach
void setUp() {
updateUser = new UpdateUser(userRepository, auditLogger, authPort);
lenient().when(unitOfWork.executeAtomically(any()))
.thenAnswer(inv -> ((Supplier<?>) inv.getArgument(0)).get());
updateUser = new UpdateUser(userRepository, auditLogger, authPort, unitOfWork);
performedBy = ActorId.of("admin-user");
testUser = User.reconstitute(
UserId.of("user-1"), "john.doe", "john@example.com",

View file

@ -3,19 +3,12 @@ package de.effigenix.infrastructure;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.domain.usermanagement.UserStatus;
import de.effigenix.infrastructure.masterdata.persistence.entity.ArticleEntity;
import de.effigenix.infrastructure.masterdata.persistence.entity.ProductCategoryEntity;
import de.effigenix.infrastructure.masterdata.persistence.repository.ArticleJpaRepository;
import de.effigenix.infrastructure.masterdata.persistence.repository.ProductCategoryJpaRepository;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import de.effigenix.infrastructure.usermanagement.persistence.repository.RoleJpaRepository;
import de.effigenix.infrastructure.usermanagement.persistence.repository.UserJpaRepository;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
@ -44,16 +37,7 @@ public abstract class AbstractIntegrationTest {
protected ObjectMapper objectMapper;
@Autowired
protected UserJpaRepository userRepository;
@Autowired
protected RoleJpaRepository roleRepository;
@Autowired
protected ArticleJpaRepository articleRepository;
@Autowired
protected ProductCategoryJpaRepository productCategoryRepository;
protected JdbcClient jdbc;
@Value("${jwt.secret}")
protected String jwtSecret;
@ -103,29 +87,70 @@ public abstract class AbstractIntegrationTest {
.compact();
}
protected RoleEntity createRole(RoleName roleName, String description) {
return roleRepository.findByName(roleName).orElseGet(() -> {
RoleEntity role = new RoleEntity(
UUID.randomUUID().toString(), roleName, Set.of(), description);
return roleRepository.save(role);
});
protected String createRole(RoleName roleName, String description) {
var existing = jdbc.sql("SELECT id FROM roles WHERE name = :name")
.param("name", roleName.name())
.query(String.class)
.optional();
if (existing.isPresent()) {
return existing.get();
}
String id = UUID.randomUUID().toString();
jdbc.sql("INSERT INTO roles (id, name, description) VALUES (:id, :name, :description)")
.param("id", id)
.param("name", roleName.name())
.param("description", description)
.update();
return id;
}
protected UserEntity createUser(String username, String email, Set<RoleEntity> roles, String branchId) {
UserEntity user = new UserEntity(
UUID.randomUUID().toString(), username, email,
BCRYPT_PASS123, roles,
branchId, UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null);
return userRepository.save(user);
protected String createUser(String username, String email, Set<String> roleIds, String branchId) {
String id = UUID.randomUUID().toString();
jdbc.sql("""
INSERT INTO users (id, username, email, password_hash, branch_id, status, created_at)
VALUES (:id, :username, :email, :passwordHash, :branchId, :status, :createdAt)
""")
.param("id", id)
.param("username", username)
.param("email", email)
.param("passwordHash", BCRYPT_PASS123)
.param("branchId", branchId)
.param("status", UserStatus.ACTIVE.name())
.param("createdAt", OffsetDateTime.now(ZoneOffset.UTC))
.update();
for (String roleId : roleIds) {
jdbc.sql("INSERT INTO user_roles (user_id, role_id) VALUES (:userId, :roleId)")
.param("userId", id)
.param("roleId", roleId)
.update();
}
return id;
}
protected String createArticleId() {
String categoryId = UUID.randomUUID().toString();
productCategoryRepository.save(new ProductCategoryEntity(categoryId, "TestCat-" + categoryId.substring(0, 8), null));
jdbc.sql("INSERT INTO product_categories (id, name) VALUES (:id, :name)")
.param("id", categoryId)
.param("name", "TestCat-" + categoryId.substring(0, 8))
.update();
String articleId = UUID.randomUUID().toString();
var now = OffsetDateTime.now(ZoneOffset.UTC);
var article = new ArticleEntity(
UUID.randomUUID().toString(), "TestArticle-" + UUID.randomUUID().toString().substring(0, 8),
"ART-" + UUID.randomUUID().toString().substring(0, 8), categoryId, "ACTIVE", now, now);
return articleRepository.save(article).getId();
jdbc.sql("""
INSERT INTO articles (id, name, article_number, category_id, status, created_at, updated_at)
VALUES (:id, :name, :articleNumber, :categoryId, :status, :createdAt, :updatedAt)
""")
.param("id", articleId)
.param("name", "TestArticle-" + UUID.randomUUID().toString().substring(0, 8))
.param("articleNumber", "ART-" + UUID.randomUUID().toString().substring(0, 8))
.param("categoryId", categoryId)
.param("status", "ACTIVE")
.param("createdAt", now)
.param("updatedAt", now)
.update();
return articleId;
}
}

View file

@ -5,8 +5,6 @@ import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.inventory.web.dto.AddStockBatchRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.ReserveStockRequest;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@ -40,14 +38,14 @@ class StockControllerIntegrationTest extends AbstractIntegrationTest {
@BeforeEach
void setUp() throws Exception {
RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin");
RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
String adminRoleId = createRole(RoleName.ADMIN, "Admin");
String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
UserEntity admin = createUser("stock.admin", "stock.admin@test.com", Set.of(adminRole), "BRANCH-01");
UserEntity viewer = createUser("stock.viewer", "stock.viewer@test.com", Set.of(viewerRole), "BRANCH-01");
String adminId = createUser("stock.admin", "stock.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
String viewerId = createUser("stock.viewer", "stock.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");
adminToken = generateToken(admin.getId(), "stock.admin", "STOCK_WRITE,STOCK_READ");
viewerToken = generateToken(viewer.getId(), "stock.viewer", "USER_READ");
adminToken = generateToken(adminId, "stock.admin", "STOCK_WRITE,STOCK_READ");
viewerToken = generateToken(viewerId, "stock.viewer", "USER_READ");
storageLocationId = createStorageLocation();
}

View file

@ -5,8 +5,6 @@ import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStorageLocationRequest;
import de.effigenix.infrastructure.inventory.web.dto.CreateStockRequest;
import de.effigenix.infrastructure.inventory.web.dto.UpdateStorageLocationRequest;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -35,16 +33,16 @@ class StorageLocationControllerIntegrationTest extends AbstractIntegrationTest {
@BeforeEach
void setUp() {
RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin");
RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
String adminRoleId = createRole(RoleName.ADMIN, "Admin");
String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
UserEntity admin = createUser("inv.admin", "inv.admin@test.com", Set.of(adminRole), "BRANCH-01");
UserEntity reader = createUser("inv.reader", "inv.reader@test.com", Set.of(viewerRole), "BRANCH-01");
UserEntity viewer = createUser("inv.viewer", "inv.viewer@test.com", Set.of(viewerRole), "BRANCH-01");
String adminId = createUser("inv.admin", "inv.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
String readerId = createUser("inv.reader", "inv.reader@test.com", Set.of(viewerRoleId), "BRANCH-01");
String viewerId = createUser("inv.viewer", "inv.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");
adminToken = generateToken(admin.getId(), "inv.admin", "STOCK_WRITE,STOCK_READ");
readerToken = generateToken(reader.getId(), "inv.reader", "STOCK_READ");
viewerToken = generateToken(viewer.getId(), "inv.viewer", "USER_READ");
adminToken = generateToken(adminId, "inv.admin", "STOCK_WRITE,STOCK_READ");
readerToken = generateToken(readerId, "inv.reader", "STOCK_READ");
viewerToken = generateToken(viewerId, "inv.viewer", "USER_READ");
}
// ==================== Lagerort anlegen Pflichtfelder ====================

View file

@ -5,8 +5,6 @@ import de.effigenix.domain.masterdata.Unit;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.masterdata.web.dto.*;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -35,14 +33,14 @@ class ArticleControllerIntegrationTest extends AbstractIntegrationTest {
@BeforeEach
void setUp() throws Exception {
RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin");
RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
String adminRoleId = createRole(RoleName.ADMIN, "Admin");
String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
UserEntity admin = createUser("art.admin", "art.admin@test.com", Set.of(adminRole), "BRANCH-01");
UserEntity viewer = createUser("art.viewer", "art.viewer@test.com", Set.of(viewerRole), "BRANCH-01");
String adminId = createUser("art.admin", "art.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
String viewerId = createUser("art.viewer", "art.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");
adminToken = generateToken(admin.getId(), "art.admin", "MASTERDATA_WRITE");
viewerToken = generateToken(viewer.getId(), "art.viewer", "USER_READ");
adminToken = generateToken(adminId, "art.admin", "MASTERDATA_WRITE");
viewerToken = generateToken(viewerId, "art.viewer", "USER_READ");
// Vorbedingung: Kategorie erstellen
categoryId = createCategory("Obst & Gemüse");

View file

@ -4,8 +4,6 @@ import de.effigenix.domain.masterdata.*;
import de.effigenix.domain.usermanagement.RoleName;
import de.effigenix.infrastructure.AbstractIntegrationTest;
import de.effigenix.infrastructure.masterdata.web.dto.*;
import de.effigenix.infrastructure.usermanagement.persistence.entity.RoleEntity;
import de.effigenix.infrastructure.usermanagement.persistence.entity.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -35,14 +33,14 @@ class CustomerControllerIntegrationTest extends AbstractIntegrationTest {
@BeforeEach
void setUp() {
RoleEntity adminRole = createRole(RoleName.ADMIN, "Admin");
RoleEntity viewerRole = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
String adminRoleId = createRole(RoleName.ADMIN, "Admin");
String viewerRoleId = createRole(RoleName.PRODUCTION_WORKER, "Viewer");
UserEntity admin = createUser("cus.admin", "cus.admin@test.com", Set.of(adminRole), "BRANCH-01");
UserEntity viewer = createUser("cus.viewer", "cus.viewer@test.com", Set.of(viewerRole), "BRANCH-01");
String adminId = createUser("cus.admin", "cus.admin@test.com", Set.of(adminRoleId), "BRANCH-01");
String viewerId = createUser("cus.viewer", "cus.viewer@test.com", Set.of(viewerRoleId), "BRANCH-01");
adminToken = generateToken(admin.getId(), "cus.admin", "MASTERDATA_WRITE");
viewerToken = generateToken(viewer.getId(), "cus.viewer", "USER_READ");
adminToken = generateToken(adminId, "cus.admin", "MASTERDATA_WRITE");
viewerToken = generateToken(viewerId, "cus.viewer", "USER_READ");
}
// ==================== TC-CUS-01: B2C-Kunde erstellen ====================

Some files were not shown because too many files have changed in this diff Show more