mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:09:35 +01:00
feat: implement Master Data BC domain model and application layer
Master Data BC als Supporting Domain für Artikel, Lieferanten und Kunden. Shared Kernel um Money, Address, ContactInfo, PaymentTerms erweitert. RepositoryError von domain.usermanagement nach shared.common migriert.
This commit is contained in:
parent
22c007a768
commit
b813fcbcaa
113 changed files with 3478 additions and 5 deletions
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class ActivateArticle {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public ActivateArticle(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(ArticleId articleId, ActorId performedBy) {
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
article.activate();
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class ActivateCustomer {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public ActivateCustomer(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
customer.activate();
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class ActivateSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public ActivateSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(SupplierId supplierId, ActorId performedBy) {
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
supplier.activate();
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.AddCertificateCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class AddCertificate {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public AddCertificate(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(AddCertificateCommand cmd, ActorId performedBy) {
|
||||
var supplierId = SupplierId.of(cmd.supplierId());
|
||||
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
|
||||
var certificate = new QualityCertificate(
|
||||
cmd.certificateType(), cmd.issuer(), cmd.validFrom(), cmd.validUntil());
|
||||
supplier.addCertificate(certificate);
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.AddDeliveryAddressCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.Address;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class AddDeliveryAddress {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public AddDeliveryAddress(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(AddDeliveryAddressCommand cmd, ActorId performedBy) {
|
||||
var customerId = CustomerId.of(cmd.customerId());
|
||||
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
|
||||
var address = new Address(cmd.street(), cmd.houseNumber(), cmd.postalCode(), cmd.city(), cmd.country());
|
||||
var deliveryAddress = new DeliveryAddress(cmd.label(), address, cmd.contactPerson(), cmd.deliveryNotes());
|
||||
customer.addDeliveryAddress(deliveryAddress);
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.AddSalesUnitCommand;
|
||||
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.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class AddSalesUnit {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public AddSalesUnit(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(AddSalesUnitCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
|
||||
var suResult = SalesUnit.create(cmd.unit(), cmd.priceModel(), Money.euro(cmd.price()));
|
||||
if (suResult.isFailure()) {
|
||||
return Result.failure(suResult.unsafeGetError());
|
||||
}
|
||||
|
||||
var addResult = article.addSalesUnit(suResult.unsafeGetValue());
|
||||
if (addResult.isFailure()) {
|
||||
return Result.failure(addResult.unsafeGetError());
|
||||
}
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.AssignSupplierCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class AssignSupplier {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public AssignSupplier(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(AssignSupplierCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
article.addSupplierReference(SupplierId.of(cmd.supplierId()));
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.CreateArticleCommand;
|
||||
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.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class CreateArticle {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public CreateArticle(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(CreateArticleCommand cmd, ActorId performedBy) {
|
||||
// Check uniqueness
|
||||
switch (articleRepository.existsByArticleNumber(new ArticleNumber(cmd.articleNumber()))) {
|
||||
case Failure<RepositoryError, Boolean> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Boolean> s -> {
|
||||
if (s.value()) {
|
||||
return Result.failure(new ArticleError.ArticleNumberAlreadyExists(cmd.articleNumber()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create initial SalesUnit
|
||||
var salesUnitResult = SalesUnit.create(cmd.unit(), cmd.priceModel(), Money.euro(cmd.price()));
|
||||
if (salesUnitResult.isFailure()) {
|
||||
return Result.failure(salesUnitResult.unsafeGetError());
|
||||
}
|
||||
|
||||
// Create Article
|
||||
var articleResult = Article.create(
|
||||
new ArticleName(cmd.name()),
|
||||
new ArticleNumber(cmd.articleNumber()),
|
||||
ProductCategoryId.of(cmd.categoryId()),
|
||||
salesUnitResult.unsafeGetValue()
|
||||
);
|
||||
if (articleResult.isFailure()) {
|
||||
return Result.failure(articleResult.unsafeGetError());
|
||||
}
|
||||
|
||||
Article article = articleResult.unsafeGetValue();
|
||||
|
||||
// Save
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.CreateCustomerCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.*;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class CreateCustomer {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public CreateCustomer(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(CreateCustomerCommand cmd, ActorId performedBy) {
|
||||
var name = new CustomerName(cmd.name());
|
||||
|
||||
switch (customerRepository.existsByName(name)) {
|
||||
case Failure<RepositoryError, Boolean> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Boolean> s -> {
|
||||
if (s.value()) {
|
||||
return Result.failure(new CustomerError.CustomerNameAlreadyExists(cmd.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var address = new Address(cmd.street(), cmd.houseNumber(), cmd.postalCode(), cmd.city(), cmd.country());
|
||||
var contactInfo = new ContactInfo(cmd.phone(), cmd.email(), cmd.contactPerson());
|
||||
var paymentTerms = new PaymentTerms(cmd.paymentDueDays(), cmd.paymentDescription());
|
||||
|
||||
var result = Customer.create(name, cmd.type(), address, contactInfo, paymentTerms);
|
||||
if (result.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Customer customer = result.unsafeGetValue();
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.CreateProductCategoryCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class CreateProductCategory {
|
||||
|
||||
private final ProductCategoryRepository categoryRepository;
|
||||
|
||||
public CreateProductCategory(ProductCategoryRepository categoryRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public Result<ProductCategoryError, ProductCategory> execute(CreateProductCategoryCommand cmd, ActorId performedBy) {
|
||||
var name = new CategoryName(cmd.name());
|
||||
|
||||
switch (categoryRepository.existsByName(name)) {
|
||||
case Failure<RepositoryError, Boolean> f ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Boolean> s -> {
|
||||
if (s.value()) {
|
||||
return Result.failure(new ProductCategoryError.CategoryNameAlreadyExists(cmd.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = ProductCategory.create(name, cmd.description());
|
||||
if (result.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ProductCategory category = result.unsafeGetValue();
|
||||
|
||||
switch (categoryRepository.save(category)) {
|
||||
case Failure<RepositoryError, Void> f ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(category);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.CreateSupplierCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.*;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class CreateSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public CreateSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(CreateSupplierCommand cmd, ActorId performedBy) {
|
||||
var name = new SupplierName(cmd.name());
|
||||
|
||||
switch (supplierRepository.existsByName(name)) {
|
||||
case Failure<RepositoryError, Boolean> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Boolean> s -> {
|
||||
if (s.value()) {
|
||||
return Result.failure(new SupplierError.SupplierNameAlreadyExists(cmd.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var address = new Address(cmd.street(), cmd.houseNumber(), cmd.postalCode(), cmd.city(), cmd.country());
|
||||
var contactInfo = new ContactInfo(cmd.phone(), cmd.email(), cmd.contactPerson());
|
||||
var paymentTerms = new PaymentTerms(cmd.paymentDueDays(), cmd.paymentDescription());
|
||||
|
||||
var result = Supplier.create(name, address, contactInfo, paymentTerms);
|
||||
if (result.isFailure()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Supplier supplier = result.unsafeGetValue();
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class DeactivateArticle {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public DeactivateArticle(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(ArticleId articleId, ActorId performedBy) {
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
article.deactivate();
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class DeactivateCustomer {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public DeactivateCustomer(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
customer.deactivate();
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class DeactivateSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public DeactivateSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(SupplierId supplierId, ActorId performedBy) {
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
supplier.deactivate();
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class DeleteProductCategory {
|
||||
|
||||
private final ProductCategoryRepository categoryRepository;
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public DeleteProductCategory(ProductCategoryRepository categoryRepository, ArticleRepository articleRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ProductCategoryError, Void> execute(ProductCategoryId categoryId, ActorId performedBy) {
|
||||
// Check category exists
|
||||
switch (categoryRepository.findById(categoryId)) {
|
||||
case Failure<RepositoryError, Optional<ProductCategory>> f ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<ProductCategory>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ProductCategoryError.CategoryNotFound(categoryId));
|
||||
}
|
||||
ProductCategory category = s.value().get();
|
||||
|
||||
// Check no articles are using this category
|
||||
switch (articleRepository.findByCategory(categoryId)) {
|
||||
case Failure<RepositoryError, List<Article>> f2 ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, List<Article>> s2 -> {
|
||||
if (!s2.value().isEmpty()) {
|
||||
return Result.failure(new ProductCategoryError.CategoryInUse(categoryId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete
|
||||
switch (categoryRepository.delete(category)) {
|
||||
case Failure<RepositoryError, Void> f3 ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f3.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetArticle {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public GetArticle(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(ArticleId articleId) {
|
||||
return switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
Result.failure(new ArticleError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, Optional<Article>> s ->
|
||||
s.value()
|
||||
.map(Result::<ArticleError, Article>success)
|
||||
.orElseGet(() -> Result.failure(new ArticleError.ArticleNotFound(articleId)));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetCustomer {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public GetCustomer(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(CustomerId customerId) {
|
||||
return switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
Result.failure(new CustomerError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, Optional<Customer>> s ->
|
||||
s.value()
|
||||
.map(Result::<CustomerError, Customer>success)
|
||||
.orElseGet(() -> Result.failure(new CustomerError.CustomerNotFound(customerId)));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public class GetSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public GetSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(SupplierId supplierId) {
|
||||
return switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
Result.failure(new SupplierError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, Optional<Supplier>> s ->
|
||||
s.value()
|
||||
.map(Result::<SupplierError, Supplier>success)
|
||||
.orElseGet(() -> Result.failure(new SupplierError.SupplierNotFound(supplierId)));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
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;
|
||||
|
||||
public ListArticles(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, List<Article>> execute() {
|
||||
return switch (articleRepository.findAll()) {
|
||||
case Failure<RepositoryError, List<Article>> f ->
|
||||
Result.failure(new ArticleError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Article>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
|
||||
public Result<ArticleError, List<Article>> executeByCategory(ProductCategoryId categoryId) {
|
||||
return switch (articleRepository.findByCategory(categoryId)) {
|
||||
case Failure<RepositoryError, List<Article>> f ->
|
||||
Result.failure(new ArticleError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Article>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
|
||||
public Result<ArticleError, List<Article>> executeByStatus(ArticleStatus status) {
|
||||
return switch (articleRepository.findByStatus(status)) {
|
||||
case Failure<RepositoryError, List<Article>> f ->
|
||||
Result.failure(new ArticleError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Article>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
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;
|
||||
|
||||
public ListCustomers(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, List<Customer>> execute() {
|
||||
return switch (customerRepository.findAll()) {
|
||||
case Failure<RepositoryError, List<Customer>> f ->
|
||||
Result.failure(new CustomerError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Customer>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
|
||||
public Result<CustomerError, List<Customer>> executeByType(CustomerType type) {
|
||||
return switch (customerRepository.findByType(type)) {
|
||||
case Failure<RepositoryError, List<Customer>> f ->
|
||||
Result.failure(new CustomerError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Customer>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
|
||||
public Result<CustomerError, List<Customer>> executeByStatus(CustomerStatus status) {
|
||||
return switch (customerRepository.findByStatus(status)) {
|
||||
case Failure<RepositoryError, List<Customer>> f ->
|
||||
Result.failure(new CustomerError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Customer>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
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;
|
||||
|
||||
public ListProductCategories(ProductCategoryRepository categoryRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public Result<ProductCategoryError, List<ProductCategory>> execute() {
|
||||
return switch (categoryRepository.findAll()) {
|
||||
case Failure<RepositoryError, List<ProductCategory>> f ->
|
||||
Result.failure(new ProductCategoryError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<ProductCategory>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
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;
|
||||
|
||||
public ListSuppliers(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, List<Supplier>> execute() {
|
||||
return switch (supplierRepository.findAll()) {
|
||||
case Failure<RepositoryError, List<Supplier>> f ->
|
||||
Result.failure(new SupplierError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Supplier>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
|
||||
public Result<SupplierError, List<Supplier>> executeByStatus(SupplierStatus status) {
|
||||
return switch (supplierRepository.findByStatus(status)) {
|
||||
case Failure<RepositoryError, List<Supplier>> f ->
|
||||
Result.failure(new SupplierError.RepositoryFailure(f.error().message()));
|
||||
case Success<RepositoryError, List<Supplier>> s ->
|
||||
Result.success(s.value());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.RateSupplierCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RateSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public RateSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(RateSupplierCommand cmd, ActorId performedBy) {
|
||||
var supplierId = SupplierId.of(cmd.supplierId());
|
||||
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
|
||||
try {
|
||||
var rating = new SupplierRating(cmd.qualityScore(), cmd.deliveryScore(), cmd.priceScore());
|
||||
supplier.rate(rating);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Result.failure(new SupplierError.InvalidRating(e.getMessage()));
|
||||
}
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.RemoveCertificateCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RemoveCertificate {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public RemoveCertificate(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(RemoveCertificateCommand cmd, ActorId performedBy) {
|
||||
var supplierId = SupplierId.of(cmd.supplierId());
|
||||
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
|
||||
// Find matching certificate by type, issuer, and validFrom
|
||||
supplier.certificates().stream()
|
||||
.filter(c -> c.certificateType().equals(cmd.certificateType())
|
||||
&& java.util.Objects.equals(c.issuer(), cmd.issuer())
|
||||
&& java.util.Objects.equals(c.validFrom(), cmd.validFrom()))
|
||||
.findFirst()
|
||||
.ifPresent(supplier::removeCertificate);
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.RemoveDeliveryAddressCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RemoveDeliveryAddress {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public RemoveDeliveryAddress(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(RemoveDeliveryAddressCommand cmd, ActorId performedBy) {
|
||||
var customerId = CustomerId.of(cmd.customerId());
|
||||
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
customer.removeDeliveryAddress(cmd.label());
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RemoveFrameContract {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public RemoveFrameContract(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(CustomerId customerId, ActorId performedBy) {
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
customer.removeFrameContract();
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.RemoveSalesUnitCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RemoveSalesUnit {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public RemoveSalesUnit(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(RemoveSalesUnitCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
|
||||
var removeResult = article.removeSalesUnit(SalesUnitId.of(cmd.salesUnitId()));
|
||||
if (removeResult.isFailure()) {
|
||||
return Result.failure(removeResult.unsafeGetError());
|
||||
}
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.RemoveSupplierCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class RemoveSupplier {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public RemoveSupplier(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(RemoveSupplierCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
article.removeSupplierReference(SupplierId.of(cmd.supplierId()));
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.SetFrameContractCommand;
|
||||
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.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class SetFrameContract {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public SetFrameContract(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(SetFrameContractCommand cmd, ActorId performedBy) {
|
||||
var customerId = CustomerId.of(cmd.customerId());
|
||||
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
|
||||
var lineItems = cmd.lineItems().stream()
|
||||
.map(li -> new ContractLineItem(
|
||||
ArticleId.of(li.articleId()),
|
||||
Money.euro(li.agreedPrice()),
|
||||
li.agreedQuantity(),
|
||||
li.unit()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
var contract = FrameContract.create(cmd.validFrom(), cmd.validUntil(), cmd.rhythm(), lineItems);
|
||||
var setResult = customer.setFrameContract(contract);
|
||||
if (setResult.isFailure()) {
|
||||
return Result.failure(setResult.unsafeGetError());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Result.failure(new CustomerError.InvalidFrameContract(e.getMessage()));
|
||||
}
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.SetPreferencesCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class SetPreferences {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public SetPreferences(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(SetPreferencesCommand cmd, ActorId performedBy) {
|
||||
var customerId = CustomerId.of(cmd.customerId());
|
||||
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
customer.setPreferences(cmd.preferences());
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.UpdateArticleCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class UpdateArticle {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public UpdateArticle(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(UpdateArticleCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
|
||||
if (cmd.name() != null) {
|
||||
article.rename(new ArticleName(cmd.name()));
|
||||
}
|
||||
if (cmd.categoryId() != null) {
|
||||
article.changeCategory(ProductCategoryId.of(cmd.categoryId()));
|
||||
}
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.UpdateCustomerCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.*;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class UpdateCustomer {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public UpdateCustomer(CustomerRepository customerRepository) {
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Result<CustomerError, Customer> execute(UpdateCustomerCommand cmd, ActorId performedBy) {
|
||||
var customerId = CustomerId.of(cmd.customerId());
|
||||
|
||||
switch (customerRepository.findById(customerId)) {
|
||||
case Failure<RepositoryError, Optional<Customer>> f ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Customer>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(customerId));
|
||||
}
|
||||
Customer customer = s.value().get();
|
||||
|
||||
if (cmd.name() != null) {
|
||||
customer.updateName(new CustomerName(cmd.name()));
|
||||
}
|
||||
customer.updateBillingAddress(new Address(cmd.street(), cmd.houseNumber(), cmd.postalCode(), cmd.city(), cmd.country()));
|
||||
customer.updateContactInfo(new ContactInfo(cmd.phone(), cmd.email(), cmd.contactPerson()));
|
||||
customer.updatePaymentTerms(new PaymentTerms(cmd.paymentDueDays(), cmd.paymentDescription()));
|
||||
|
||||
switch (customerRepository.save(customer)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new CustomerError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
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.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class UpdateProductCategory {
|
||||
|
||||
private final ProductCategoryRepository categoryRepository;
|
||||
|
||||
public UpdateProductCategory(ProductCategoryRepository categoryRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public Result<ProductCategoryError, ProductCategory> execute(UpdateProductCategoryCommand cmd, ActorId performedBy) {
|
||||
var categoryId = ProductCategoryId.of(cmd.categoryId());
|
||||
|
||||
switch (categoryRepository.findById(categoryId)) {
|
||||
case Failure<RepositoryError, Optional<ProductCategory>> f ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<ProductCategory>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ProductCategoryError.CategoryNotFound(categoryId));
|
||||
}
|
||||
ProductCategory category = s.value().get();
|
||||
|
||||
if (cmd.name() != null) {
|
||||
category.rename(new CategoryName(cmd.name()));
|
||||
}
|
||||
if (cmd.description() != null) {
|
||||
category.updateDescription(cmd.description());
|
||||
}
|
||||
|
||||
switch (categoryRepository.save(category)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ProductCategoryError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.UpdateSalesUnitPriceCommand;
|
||||
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.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class UpdateSalesUnitPrice {
|
||||
|
||||
private final ArticleRepository articleRepository;
|
||||
|
||||
public UpdateSalesUnitPrice(ArticleRepository articleRepository) {
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
public Result<ArticleError, Article> execute(UpdateSalesUnitPriceCommand cmd, ActorId performedBy) {
|
||||
var articleId = ArticleId.of(cmd.articleId());
|
||||
|
||||
switch (articleRepository.findById(articleId)) {
|
||||
case Failure<RepositoryError, Optional<Article>> f ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Article>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new ArticleError.ArticleNotFound(articleId));
|
||||
}
|
||||
Article article = s.value().get();
|
||||
|
||||
var updateResult = article.updateSalesUnitPrice(
|
||||
SalesUnitId.of(cmd.salesUnitId()), Money.euro(cmd.price()));
|
||||
if (updateResult.isFailure()) {
|
||||
return Result.failure(updateResult.unsafeGetError());
|
||||
}
|
||||
|
||||
switch (articleRepository.save(article)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new ArticleError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package de.effigenix.application.masterdata;
|
||||
|
||||
import de.effigenix.application.masterdata.command.UpdateSupplierCommand;
|
||||
import de.effigenix.domain.masterdata.*;
|
||||
import de.effigenix.shared.common.*;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.effigenix.shared.common.Result.*;
|
||||
|
||||
@Transactional
|
||||
public class UpdateSupplier {
|
||||
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
public UpdateSupplier(SupplierRepository supplierRepository) {
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public Result<SupplierError, Supplier> execute(UpdateSupplierCommand cmd, ActorId performedBy) {
|
||||
var supplierId = SupplierId.of(cmd.supplierId());
|
||||
|
||||
switch (supplierRepository.findById(supplierId)) {
|
||||
case Failure<RepositoryError, Optional<Supplier>> f ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f.error().message())); }
|
||||
case Success<RepositoryError, Optional<Supplier>> s -> {
|
||||
if (s.value().isEmpty()) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(supplierId));
|
||||
}
|
||||
Supplier supplier = s.value().get();
|
||||
|
||||
if (cmd.name() != null) {
|
||||
supplier.updateName(new SupplierName(cmd.name()));
|
||||
}
|
||||
supplier.updateAddress(new Address(cmd.street(), cmd.houseNumber(), cmd.postalCode(), cmd.city(), cmd.country()));
|
||||
supplier.updateContactInfo(new ContactInfo(cmd.phone(), cmd.email(), cmd.contactPerson()));
|
||||
supplier.updatePaymentTerms(new PaymentTerms(cmd.paymentDueDays(), cmd.paymentDescription()));
|
||||
|
||||
switch (supplierRepository.save(supplier)) {
|
||||
case Failure<RepositoryError, Void> f2 ->
|
||||
{ return Result.failure(new SupplierError.RepositoryFailure(f2.error().message())); }
|
||||
case Success<RepositoryError, Void> ignored -> { }
|
||||
}
|
||||
|
||||
return Result.success(supplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record AddCertificateCommand(
|
||||
String supplierId,
|
||||
String certificateType,
|
||||
String issuer,
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil
|
||||
) {}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record AddDeliveryAddressCommand(
|
||||
String customerId,
|
||||
String label,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String country,
|
||||
String contactPerson,
|
||||
String deliveryNotes
|
||||
) {}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import de.effigenix.domain.masterdata.PriceModel;
|
||||
import de.effigenix.domain.masterdata.Unit;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public record AddSalesUnitCommand(
|
||||
String articleId,
|
||||
Unit unit,
|
||||
PriceModel priceModel,
|
||||
BigDecimal price
|
||||
) {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record AssignSupplierCommand(
|
||||
String articleId,
|
||||
String supplierId
|
||||
) {}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import de.effigenix.domain.masterdata.PriceModel;
|
||||
import de.effigenix.domain.masterdata.Unit;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public record CreateArticleCommand(
|
||||
String name,
|
||||
String articleNumber,
|
||||
String categoryId,
|
||||
Unit unit,
|
||||
PriceModel priceModel,
|
||||
BigDecimal price
|
||||
) {}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import de.effigenix.domain.masterdata.CustomerType;
|
||||
|
||||
public record CreateCustomerCommand(
|
||||
String name,
|
||||
CustomerType type,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String country,
|
||||
String phone,
|
||||
String email,
|
||||
String contactPerson,
|
||||
int paymentDueDays,
|
||||
String paymentDescription
|
||||
) {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record CreateProductCategoryCommand(
|
||||
String name,
|
||||
String description
|
||||
) {}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record CreateSupplierCommand(
|
||||
String name,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String country,
|
||||
String phone,
|
||||
String email,
|
||||
String contactPerson,
|
||||
int paymentDueDays,
|
||||
String paymentDescription
|
||||
) {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record RateSupplierCommand(
|
||||
String supplierId,
|
||||
int qualityScore,
|
||||
int deliveryScore,
|
||||
int priceScore
|
||||
) {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record RemoveCertificateCommand(
|
||||
String supplierId,
|
||||
String certificateType,
|
||||
String issuer,
|
||||
LocalDate validFrom
|
||||
) {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record RemoveDeliveryAddressCommand(
|
||||
String customerId,
|
||||
String label
|
||||
) {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record RemoveSalesUnitCommand(
|
||||
String articleId,
|
||||
String salesUnitId
|
||||
) {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record RemoveSupplierCommand(
|
||||
String articleId,
|
||||
String supplierId
|
||||
) {}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import de.effigenix.domain.masterdata.DeliveryRhythm;
|
||||
import de.effigenix.domain.masterdata.Unit;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public record SetFrameContractCommand(
|
||||
String customerId,
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil,
|
||||
DeliveryRhythm rhythm,
|
||||
List<LineItem> lineItems
|
||||
) {
|
||||
public record LineItem(
|
||||
String articleId,
|
||||
BigDecimal agreedPrice,
|
||||
BigDecimal agreedQuantity,
|
||||
Unit unit
|
||||
) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import de.effigenix.domain.masterdata.CustomerPreference;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record SetPreferencesCommand(
|
||||
String customerId,
|
||||
Set<CustomerPreference> preferences
|
||||
) {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record UpdateArticleCommand(
|
||||
String articleId,
|
||||
String name,
|
||||
String categoryId
|
||||
) {}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record UpdateCustomerCommand(
|
||||
String customerId,
|
||||
String name,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String country,
|
||||
String phone,
|
||||
String email,
|
||||
String contactPerson,
|
||||
int paymentDueDays,
|
||||
String paymentDescription
|
||||
) {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record UpdateProductCategoryCommand(
|
||||
String categoryId,
|
||||
String name,
|
||||
String description
|
||||
) {}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public record UpdateSalesUnitPriceCommand(
|
||||
String articleId,
|
||||
String salesUnitId,
|
||||
BigDecimal price
|
||||
) {}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package de.effigenix.application.masterdata.command;
|
||||
|
||||
public record UpdateSupplierCommand(
|
||||
String supplierId,
|
||||
String name,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String country,
|
||||
String phone,
|
||||
String email,
|
||||
String contactPerson,
|
||||
int paymentDueDays,
|
||||
String paymentDescription
|
||||
) {}
|
||||
|
|
@ -3,6 +3,7 @@ package de.effigenix.application.usermanagement;
|
|||
import de.effigenix.application.usermanagement.command.AssignRoleCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package de.effigenix.application.usermanagement;
|
|||
import de.effigenix.application.usermanagement.command.AuthenticateCommand;
|
||||
import de.effigenix.application.usermanagement.dto.SessionToken;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.effigenix.application.usermanagement;
|
|||
|
||||
import de.effigenix.application.usermanagement.command.ChangePasswordCommand;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package de.effigenix.application.usermanagement;
|
|||
import de.effigenix.application.usermanagement.command.CreateUserCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.effigenix.application.usermanagement;
|
|||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.RepositoryError;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.domain.usermanagement.User;
|
||||
import de.effigenix.domain.usermanagement.UserError;
|
||||
import de.effigenix.domain.usermanagement.UserRepository;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.effigenix.application.usermanagement;
|
|||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.effigenix.application.usermanagement;
|
|||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package de.effigenix.application.usermanagement;
|
|||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package de.effigenix.application.usermanagement;
|
|||
import de.effigenix.application.usermanagement.command.UpdateUserCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Money;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Aggregate Root for Article.
|
||||
*
|
||||
* Invariants:
|
||||
* 1. At least one SalesUnit
|
||||
* 2. No duplicate Unit types
|
||||
* 3. Unit/PriceModel consistency (enforced by SalesUnit)
|
||||
* 4. ArticleNumber uniqueness (enforced at Repository level)
|
||||
*/
|
||||
public class Article {
|
||||
|
||||
private final ArticleId id;
|
||||
private ArticleName name;
|
||||
private final ArticleNumber articleNumber;
|
||||
private ProductCategoryId categoryId;
|
||||
private final List<SalesUnit> salesUnits;
|
||||
private ArticleStatus status;
|
||||
private final Set<SupplierId> supplierReferences;
|
||||
private final LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
private Article(
|
||||
ArticleId id,
|
||||
ArticleName name,
|
||||
ArticleNumber articleNumber,
|
||||
ProductCategoryId categoryId,
|
||||
List<SalesUnit> salesUnits,
|
||||
ArticleStatus status,
|
||||
Set<SupplierId> supplierReferences,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.articleNumber = articleNumber;
|
||||
this.categoryId = categoryId;
|
||||
this.salesUnits = new ArrayList<>(salesUnits);
|
||||
this.status = status;
|
||||
this.supplierReferences = new HashSet<>(supplierReferences);
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public static Result<ArticleError, Article> create(
|
||||
ArticleName name,
|
||||
ArticleNumber articleNumber,
|
||||
ProductCategoryId categoryId,
|
||||
SalesUnit initialSalesUnit
|
||||
) {
|
||||
if (name == null || articleNumber == null || categoryId == null || initialSalesUnit == null) {
|
||||
return Result.failure(new ArticleError.MinimumSalesUnitRequired());
|
||||
}
|
||||
var now = LocalDateTime.now();
|
||||
return Result.success(new Article(
|
||||
ArticleId.generate(),
|
||||
name,
|
||||
articleNumber,
|
||||
categoryId,
|
||||
List.of(initialSalesUnit),
|
||||
ArticleStatus.ACTIVE,
|
||||
Set.of(),
|
||||
now,
|
||||
now
|
||||
));
|
||||
}
|
||||
|
||||
public static Article reconstitute(
|
||||
ArticleId id,
|
||||
ArticleName name,
|
||||
ArticleNumber articleNumber,
|
||||
ProductCategoryId categoryId,
|
||||
List<SalesUnit> salesUnits,
|
||||
ArticleStatus status,
|
||||
Set<SupplierId> supplierReferences,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
return new Article(id, name, articleNumber, categoryId, salesUnits, status,
|
||||
supplierReferences, createdAt, updatedAt);
|
||||
}
|
||||
|
||||
// ==================== Sales Unit Management ====================
|
||||
|
||||
public Result<ArticleError, Void> addSalesUnit(SalesUnit salesUnit) {
|
||||
if (hasUnitType(salesUnit.unit())) {
|
||||
return Result.failure(new ArticleError.DuplicateSalesUnitType(salesUnit.unit()));
|
||||
}
|
||||
this.salesUnits.add(salesUnit);
|
||||
touch();
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
public Result<ArticleError, Void> removeSalesUnit(SalesUnitId salesUnitId) {
|
||||
if (this.salesUnits.size() <= 1) {
|
||||
return Result.failure(new ArticleError.MinimumSalesUnitRequired());
|
||||
}
|
||||
boolean removed = this.salesUnits.removeIf(su -> su.id().equals(salesUnitId));
|
||||
if (!removed) {
|
||||
return Result.failure(new ArticleError.SalesUnitNotFound(salesUnitId));
|
||||
}
|
||||
touch();
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
public Result<ArticleError, Void> updateSalesUnitPrice(SalesUnitId salesUnitId, Money newPrice) {
|
||||
var salesUnit = findSalesUnit(salesUnitId);
|
||||
if (salesUnit == null) {
|
||||
return Result.failure(new ArticleError.SalesUnitNotFound(salesUnitId));
|
||||
}
|
||||
var result = salesUnit.updatePrice(newPrice);
|
||||
if (result.isSuccess()) {
|
||||
touch();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== Article Properties ====================
|
||||
|
||||
public void rename(ArticleName newName) {
|
||||
this.name = newName;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void changeCategory(ProductCategoryId newCategoryId) {
|
||||
this.categoryId = newCategoryId;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
this.status = ArticleStatus.ACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
this.status = ArticleStatus.INACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Supplier References ====================
|
||||
|
||||
public void addSupplierReference(SupplierId supplierId) {
|
||||
this.supplierReferences.add(supplierId);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void removeSupplierReference(SupplierId supplierId) {
|
||||
this.supplierReferences.remove(supplierId);
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
private boolean hasUnitType(Unit unit) {
|
||||
return salesUnits.stream().anyMatch(su -> su.unit() == unit);
|
||||
}
|
||||
|
||||
private SalesUnit findSalesUnit(SalesUnitId id) {
|
||||
return salesUnits.stream()
|
||||
.filter(su -> su.id().equals(id))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private void touch() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// ==================== Getters ====================
|
||||
|
||||
public ArticleId id() { return id; }
|
||||
public ArticleName name() { return name; }
|
||||
public ArticleNumber articleNumber() { return articleNumber; }
|
||||
public ProductCategoryId categoryId() { return categoryId; }
|
||||
public List<SalesUnit> salesUnits() { return Collections.unmodifiableList(salesUnits); }
|
||||
public ArticleStatus status() { return status; }
|
||||
public Set<SupplierId> supplierReferences() { return Collections.unmodifiableSet(supplierReferences); }
|
||||
public LocalDateTime createdAt() { return createdAt; }
|
||||
public LocalDateTime updatedAt() { return updatedAt; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Article other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Article{id=" + id + ", name=" + name + ", number=" + articleNumber + ", status=" + status + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public sealed interface ArticleError {
|
||||
|
||||
String code();
|
||||
String message();
|
||||
|
||||
record ArticleNotFound(ArticleId id) implements ArticleError {
|
||||
@Override public String code() { return "ARTICLE_NOT_FOUND"; }
|
||||
@Override public String message() { return "Article with ID '" + id.value() + "' not found"; }
|
||||
}
|
||||
|
||||
record ArticleNumberAlreadyExists(String articleNumber) implements ArticleError {
|
||||
@Override public String code() { return "ARTICLE_NUMBER_EXISTS"; }
|
||||
@Override public String message() { return "Article number '" + articleNumber + "' already exists"; }
|
||||
}
|
||||
|
||||
record SalesUnitNotFound(SalesUnitId id) implements ArticleError {
|
||||
@Override public String code() { return "SALES_UNIT_NOT_FOUND"; }
|
||||
@Override public String message() { return "SalesUnit with ID '" + id.value() + "' not found"; }
|
||||
}
|
||||
|
||||
record MinimumSalesUnitRequired() implements ArticleError {
|
||||
@Override public String code() { return "MINIMUM_SALES_UNIT"; }
|
||||
@Override public String message() { return "Article must have at least one SalesUnit"; }
|
||||
}
|
||||
|
||||
record DuplicateSalesUnitType(Unit unit) implements ArticleError {
|
||||
@Override public String code() { return "DUPLICATE_SALES_UNIT_TYPE"; }
|
||||
@Override public String message() { return "Article already has a SalesUnit of type " + unit; }
|
||||
}
|
||||
|
||||
record InvalidPriceModelCombination(String reason) implements ArticleError {
|
||||
@Override public String code() { return "INVALID_PRICE_MODEL"; }
|
||||
@Override public String message() { return "Invalid Unit/PriceModel combination: " + reason; }
|
||||
}
|
||||
|
||||
record InvalidPrice(String reason) implements ArticleError {
|
||||
@Override public String code() { return "INVALID_PRICE"; }
|
||||
@Override public String message() { return "Invalid price: " + reason; }
|
||||
}
|
||||
|
||||
record Unauthorized(String message) implements ArticleError {
|
||||
@Override public String code() { return "UNAUTHORIZED"; }
|
||||
}
|
||||
|
||||
record RepositoryFailure(String message) implements ArticleError {
|
||||
@Override public String code() { return "REPOSITORY_ERROR"; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record ArticleId(String value) {
|
||||
|
||||
public ArticleId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("ArticleId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static ArticleId generate() {
|
||||
return new ArticleId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static ArticleId of(String value) {
|
||||
return new ArticleId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public record ArticleName(String value) {
|
||||
|
||||
public ArticleName {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("ArticleName must not be blank");
|
||||
}
|
||||
if (value.length() > 200) {
|
||||
throw new IllegalArgumentException("ArticleName must not exceed 200 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public record ArticleNumber(String value) {
|
||||
|
||||
public ArticleNumber {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("ArticleNumber must not be blank");
|
||||
}
|
||||
if (value.length() > 50) {
|
||||
throw new IllegalArgumentException("ArticleNumber must not exceed 50 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ArticleRepository {
|
||||
|
||||
Result<RepositoryError, Optional<Article>> findById(ArticleId id);
|
||||
|
||||
Result<RepositoryError, List<Article>> findAll();
|
||||
|
||||
Result<RepositoryError, List<Article>> findByCategory(ProductCategoryId categoryId);
|
||||
|
||||
Result<RepositoryError, List<Article>> findByStatus(ArticleStatus status);
|
||||
|
||||
Result<RepositoryError, Void> save(Article article);
|
||||
|
||||
Result<RepositoryError, Void> delete(Article article);
|
||||
|
||||
Result<RepositoryError, Boolean> existsByArticleNumber(ArticleNumber articleNumber);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum ArticleStatus {
|
||||
ACTIVE,
|
||||
INACTIVE
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public record CategoryName(String value) {
|
||||
|
||||
public CategoryName {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("CategoryName must not be blank");
|
||||
}
|
||||
if (value.length() > 100) {
|
||||
throw new IllegalArgumentException("CategoryName must not exceed 100 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Money;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Value Object representing a line item in a frame contract.
|
||||
*/
|
||||
public record ContractLineItem(
|
||||
ArticleId articleId,
|
||||
Money agreedPrice,
|
||||
BigDecimal agreedQuantity,
|
||||
Unit unit
|
||||
) {
|
||||
|
||||
public ContractLineItem {
|
||||
if (articleId == null) {
|
||||
throw new IllegalArgumentException("ArticleId must not be null");
|
||||
}
|
||||
if (agreedPrice == null) {
|
||||
throw new IllegalArgumentException("Agreed price must not be null");
|
||||
}
|
||||
if (agreedQuantity != null && agreedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("Agreed quantity must be positive when set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
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 de.effigenix.shared.common.Result;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Aggregate Root for Customer.
|
||||
*
|
||||
* Invariants:
|
||||
* 1. Name, CustomerType, billingAddress non-null
|
||||
* 2. FrameContract only for B2B customers
|
||||
* 3. No duplicate ContractLineItems per ArticleId (enforced by FrameContract)
|
||||
*/
|
||||
public class Customer {
|
||||
|
||||
private final CustomerId id;
|
||||
private CustomerName name;
|
||||
private final CustomerType type;
|
||||
private Address billingAddress;
|
||||
private ContactInfo contactInfo;
|
||||
private PaymentTerms paymentTerms;
|
||||
private final List<DeliveryAddress> deliveryAddresses;
|
||||
private FrameContract frameContract;
|
||||
private final Set<CustomerPreference> preferences;
|
||||
private CustomerStatus status;
|
||||
private final LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
private Customer(
|
||||
CustomerId id,
|
||||
CustomerName name,
|
||||
CustomerType type,
|
||||
Address billingAddress,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms,
|
||||
List<DeliveryAddress> deliveryAddresses,
|
||||
FrameContract frameContract,
|
||||
Set<CustomerPreference> preferences,
|
||||
CustomerStatus status,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.billingAddress = billingAddress;
|
||||
this.contactInfo = contactInfo;
|
||||
this.paymentTerms = paymentTerms;
|
||||
this.deliveryAddresses = new ArrayList<>(deliveryAddresses);
|
||||
this.frameContract = frameContract;
|
||||
this.preferences = new HashSet<>(preferences);
|
||||
this.status = status;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public static Result<CustomerError, Customer> create(
|
||||
CustomerName name,
|
||||
CustomerType type,
|
||||
Address billingAddress,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms
|
||||
) {
|
||||
if (name == null || type == null || billingAddress == null) {
|
||||
return Result.failure(new CustomerError.CustomerNotFound(CustomerId.of("N/A")));
|
||||
}
|
||||
var now = LocalDateTime.now();
|
||||
return Result.success(new Customer(
|
||||
CustomerId.generate(), name, type, billingAddress, contactInfo, paymentTerms,
|
||||
List.of(), null, Set.of(), CustomerStatus.ACTIVE, now, now
|
||||
));
|
||||
}
|
||||
|
||||
public static Customer reconstitute(
|
||||
CustomerId id,
|
||||
CustomerName name,
|
||||
CustomerType type,
|
||||
Address billingAddress,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms,
|
||||
List<DeliveryAddress> deliveryAddresses,
|
||||
FrameContract frameContract,
|
||||
Set<CustomerPreference> preferences,
|
||||
CustomerStatus status,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
return new Customer(id, name, type, billingAddress, contactInfo, paymentTerms,
|
||||
deliveryAddresses, frameContract, preferences, status, createdAt, updatedAt);
|
||||
}
|
||||
|
||||
// ==================== Business Methods ====================
|
||||
|
||||
public void updateName(CustomerName newName) {
|
||||
this.name = newName;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updateBillingAddress(Address newAddress) {
|
||||
this.billingAddress = newAddress;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updateContactInfo(ContactInfo newContactInfo) {
|
||||
this.contactInfo = newContactInfo;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updatePaymentTerms(PaymentTerms newPaymentTerms) {
|
||||
this.paymentTerms = newPaymentTerms;
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Delivery Addresses ====================
|
||||
|
||||
public void addDeliveryAddress(DeliveryAddress address) {
|
||||
this.deliveryAddresses.add(address);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void removeDeliveryAddress(String label) {
|
||||
this.deliveryAddresses.removeIf(da ->
|
||||
Objects.equals(da.label(), label));
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Frame Contract ====================
|
||||
|
||||
public Result<CustomerError, Void> setFrameContract(FrameContract contract) {
|
||||
if (this.type != CustomerType.B2B) {
|
||||
return Result.failure(new CustomerError.FrameContractNotAllowed());
|
||||
}
|
||||
this.frameContract = contract;
|
||||
touch();
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
public void removeFrameContract() {
|
||||
this.frameContract = null;
|
||||
touch();
|
||||
}
|
||||
|
||||
public boolean hasActiveFrameContract() {
|
||||
return frameContract != null && frameContract.isActive();
|
||||
}
|
||||
|
||||
public Optional<Money> getAgreedPrice(ArticleId articleId) {
|
||||
if (frameContract == null) return Optional.empty();
|
||||
return frameContract.getAgreedPrice(articleId);
|
||||
}
|
||||
|
||||
// ==================== Preferences ====================
|
||||
|
||||
public void addPreference(CustomerPreference preference) {
|
||||
this.preferences.add(preference);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void removePreference(CustomerPreference preference) {
|
||||
this.preferences.remove(preference);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void setPreferences(Set<CustomerPreference> newPreferences) {
|
||||
this.preferences.clear();
|
||||
this.preferences.addAll(newPreferences);
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Status ====================
|
||||
|
||||
public void activate() {
|
||||
this.status = CustomerStatus.ACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
this.status = CustomerStatus.INACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
private void touch() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// ==================== Getters ====================
|
||||
|
||||
public CustomerId id() { return id; }
|
||||
public CustomerName name() { return name; }
|
||||
public CustomerType type() { return type; }
|
||||
public Address billingAddress() { return billingAddress; }
|
||||
public ContactInfo contactInfo() { return contactInfo; }
|
||||
public PaymentTerms paymentTerms() { return paymentTerms; }
|
||||
public List<DeliveryAddress> deliveryAddresses() { return Collections.unmodifiableList(deliveryAddresses); }
|
||||
public FrameContract frameContract() { return frameContract; }
|
||||
public Set<CustomerPreference> preferences() { return Collections.unmodifiableSet(preferences); }
|
||||
public CustomerStatus status() { return status; }
|
||||
public LocalDateTime createdAt() { return createdAt; }
|
||||
public LocalDateTime updatedAt() { return updatedAt; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Customer other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Customer{id=" + id + ", name=" + name + ", type=" + type + ", status=" + status + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public sealed interface CustomerError {
|
||||
|
||||
String code();
|
||||
String message();
|
||||
|
||||
record CustomerNotFound(CustomerId id) implements CustomerError {
|
||||
@Override public String code() { return "CUSTOMER_NOT_FOUND"; }
|
||||
@Override public String message() { return "Customer with ID '" + id.value() + "' not found"; }
|
||||
}
|
||||
|
||||
record CustomerNameAlreadyExists(String name) implements CustomerError {
|
||||
@Override public String code() { return "CUSTOMER_NAME_EXISTS"; }
|
||||
@Override public String message() { return "Customer name '" + name + "' already exists"; }
|
||||
}
|
||||
|
||||
record FrameContractNotAllowed() implements CustomerError {
|
||||
@Override public String code() { return "FRAME_CONTRACT_NOT_ALLOWED"; }
|
||||
@Override public String message() { return "Frame contracts are only allowed for B2B customers"; }
|
||||
}
|
||||
|
||||
record InvalidFrameContract(String reason) implements CustomerError {
|
||||
@Override public String code() { return "INVALID_FRAME_CONTRACT"; }
|
||||
@Override public String message() { return "Invalid frame contract: " + reason; }
|
||||
}
|
||||
|
||||
record DuplicateContractLineItem(ArticleId articleId) implements CustomerError {
|
||||
@Override public String code() { return "DUPLICATE_LINE_ITEM"; }
|
||||
@Override public String message() { return "Duplicate line item for article '" + articleId.value() + "'"; }
|
||||
}
|
||||
|
||||
record Unauthorized(String message) implements CustomerError {
|
||||
@Override public String code() { return "UNAUTHORIZED"; }
|
||||
}
|
||||
|
||||
record RepositoryFailure(String message) implements CustomerError {
|
||||
@Override public String code() { return "REPOSITORY_ERROR"; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record CustomerId(String value) {
|
||||
|
||||
public CustomerId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("CustomerId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomerId generate() {
|
||||
return new CustomerId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static CustomerId of(String value) {
|
||||
return new CustomerId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public record CustomerName(String value) {
|
||||
|
||||
public CustomerName {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("CustomerName must not be blank");
|
||||
}
|
||||
if (value.length() > 200) {
|
||||
throw new IllegalArgumentException("CustomerName must not exceed 200 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum CustomerPreference {
|
||||
BIO,
|
||||
REGIONAL,
|
||||
TIERWOHL,
|
||||
HALAL,
|
||||
KOSHER,
|
||||
GLUTENFREI,
|
||||
LAKTOSEFREI
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CustomerRepository {
|
||||
|
||||
Result<RepositoryError, Optional<Customer>> findById(CustomerId id);
|
||||
|
||||
Result<RepositoryError, List<Customer>> findAll();
|
||||
|
||||
Result<RepositoryError, List<Customer>> findByType(CustomerType type);
|
||||
|
||||
Result<RepositoryError, List<Customer>> findByStatus(CustomerStatus status);
|
||||
|
||||
Result<RepositoryError, Void> save(Customer customer);
|
||||
|
||||
Result<RepositoryError, Void> delete(Customer customer);
|
||||
|
||||
Result<RepositoryError, Boolean> existsByName(CustomerName name);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum CustomerStatus {
|
||||
ACTIVE,
|
||||
INACTIVE
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum CustomerType {
|
||||
B2C,
|
||||
B2B
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Address;
|
||||
|
||||
/**
|
||||
* Value Object representing a labeled delivery address.
|
||||
*/
|
||||
public record DeliveryAddress(
|
||||
String label,
|
||||
Address address,
|
||||
String contactPerson,
|
||||
String deliveryNotes
|
||||
) {
|
||||
|
||||
public DeliveryAddress {
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address must not be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum DeliveryRhythm {
|
||||
DAILY,
|
||||
WEEKLY,
|
||||
BIWEEKLY,
|
||||
MONTHLY,
|
||||
ON_DEMAND
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Money;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Entity representing a frame contract within a Customer aggregate.
|
||||
* No own lifecycle — belongs to Customer (only B2B).
|
||||
*
|
||||
* Invariants:
|
||||
* - validUntil >= validFrom
|
||||
* - No duplicate ArticleIds in lineItems
|
||||
* - At least one lineItem
|
||||
*/
|
||||
public class FrameContract {
|
||||
|
||||
private final FrameContractId id;
|
||||
private final LocalDate validFrom;
|
||||
private final LocalDate validUntil;
|
||||
private final DeliveryRhythm deliveryRhythm;
|
||||
private final List<ContractLineItem> lineItems;
|
||||
|
||||
private FrameContract(
|
||||
FrameContractId id,
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil,
|
||||
DeliveryRhythm deliveryRhythm,
|
||||
List<ContractLineItem> lineItems
|
||||
) {
|
||||
this.id = id;
|
||||
this.validFrom = validFrom;
|
||||
this.validUntil = validUntil;
|
||||
this.deliveryRhythm = deliveryRhythm;
|
||||
this.lineItems = new ArrayList<>(lineItems);
|
||||
}
|
||||
|
||||
public static FrameContract create(
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil,
|
||||
DeliveryRhythm deliveryRhythm,
|
||||
List<ContractLineItem> lineItems
|
||||
) {
|
||||
if (validFrom != null && validUntil != null && validUntil.isBefore(validFrom)) {
|
||||
throw new IllegalArgumentException("validUntil must not be before validFrom");
|
||||
}
|
||||
if (lineItems == null || lineItems.isEmpty()) {
|
||||
throw new IllegalArgumentException("FrameContract must have at least one line item");
|
||||
}
|
||||
long distinctArticles = lineItems.stream()
|
||||
.map(ContractLineItem::articleId)
|
||||
.distinct()
|
||||
.count();
|
||||
if (distinctArticles != lineItems.size()) {
|
||||
throw new IllegalArgumentException("Duplicate ArticleIds in line items");
|
||||
}
|
||||
return new FrameContract(FrameContractId.generate(), validFrom, validUntil, deliveryRhythm, lineItems);
|
||||
}
|
||||
|
||||
public static FrameContract reconstitute(
|
||||
FrameContractId id,
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil,
|
||||
DeliveryRhythm deliveryRhythm,
|
||||
List<ContractLineItem> lineItems
|
||||
) {
|
||||
return new FrameContract(id, validFrom, validUntil, deliveryRhythm, lineItems);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
var today = LocalDate.now();
|
||||
return (validFrom == null || !today.isBefore(validFrom))
|
||||
&& (validUntil == null || !today.isAfter(validUntil));
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return validUntil != null && LocalDate.now().isAfter(validUntil);
|
||||
}
|
||||
|
||||
public Optional<ContractLineItem> findLineItem(ArticleId articleId) {
|
||||
return lineItems.stream()
|
||||
.filter(li -> li.articleId().equals(articleId))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<Money> getAgreedPrice(ArticleId articleId) {
|
||||
return findLineItem(articleId).map(ContractLineItem::agreedPrice);
|
||||
}
|
||||
|
||||
public FrameContractId id() { return id; }
|
||||
public LocalDate validFrom() { return validFrom; }
|
||||
public LocalDate validUntil() { return validUntil; }
|
||||
public DeliveryRhythm deliveryRhythm() { return deliveryRhythm; }
|
||||
public List<ContractLineItem> lineItems() { return Collections.unmodifiableList(lineItems); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FrameContract other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record FrameContractId(String value) {
|
||||
|
||||
public FrameContractId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("FrameContractId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static FrameContractId generate() {
|
||||
return new FrameContractId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static FrameContractId of(String value) {
|
||||
return new FrameContractId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.security.Action;
|
||||
|
||||
public enum MasterDataAction implements Action {
|
||||
CREATE_ARTICLE,
|
||||
UPDATE_ARTICLE,
|
||||
DELETE_ARTICLE,
|
||||
VIEW_ARTICLE,
|
||||
CREATE_SUPPLIER,
|
||||
UPDATE_SUPPLIER,
|
||||
DELETE_SUPPLIER,
|
||||
VIEW_SUPPLIER,
|
||||
RATE_SUPPLIER,
|
||||
CREATE_CUSTOMER,
|
||||
UPDATE_CUSTOMER,
|
||||
DELETE_CUSTOMER,
|
||||
VIEW_CUSTOMER,
|
||||
MANAGE_FRAME_CONTRACT,
|
||||
CREATE_CATEGORY,
|
||||
UPDATE_CATEGORY,
|
||||
DELETE_CATEGORY,
|
||||
VIEW_CATEGORY
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public enum PriceModel {
|
||||
FIXED,
|
||||
WEIGHT_BASED
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
/**
|
||||
* Mini-Aggregate for product categories.
|
||||
* DB-based entity, dynamically extendable by users.
|
||||
*/
|
||||
public class ProductCategory {
|
||||
|
||||
private final ProductCategoryId id;
|
||||
private CategoryName name;
|
||||
private String description;
|
||||
|
||||
private ProductCategory(ProductCategoryId id, CategoryName name, String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static Result<ProductCategoryError, ProductCategory> create(CategoryName name, String description) {
|
||||
if (name == null) {
|
||||
return Result.failure(new ProductCategoryError.InvalidCategoryName("Name must not be null"));
|
||||
}
|
||||
return Result.success(new ProductCategory(ProductCategoryId.generate(), name, description));
|
||||
}
|
||||
|
||||
public static ProductCategory reconstitute(ProductCategoryId id, CategoryName name, String description) {
|
||||
return new ProductCategory(id, name, description);
|
||||
}
|
||||
|
||||
public void rename(CategoryName newName) {
|
||||
this.name = newName;
|
||||
}
|
||||
|
||||
public void updateDescription(String newDescription) {
|
||||
this.description = newDescription;
|
||||
}
|
||||
|
||||
public ProductCategoryId id() { return id; }
|
||||
public CategoryName name() { return name; }
|
||||
public String description() { return description; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof ProductCategory other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProductCategory{id=" + id + ", name=" + name + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public sealed interface ProductCategoryError {
|
||||
|
||||
String code();
|
||||
String message();
|
||||
|
||||
record CategoryNotFound(ProductCategoryId id) implements ProductCategoryError {
|
||||
@Override public String code() { return "CATEGORY_NOT_FOUND"; }
|
||||
@Override public String message() { return "ProductCategory with ID '" + id.value() + "' not found"; }
|
||||
}
|
||||
|
||||
record CategoryNameAlreadyExists(String name) implements ProductCategoryError {
|
||||
@Override public String code() { return "CATEGORY_NAME_EXISTS"; }
|
||||
@Override public String message() { return "Category name '" + name + "' already exists"; }
|
||||
}
|
||||
|
||||
record CategoryInUse(ProductCategoryId id) implements ProductCategoryError {
|
||||
@Override public String code() { return "CATEGORY_IN_USE"; }
|
||||
@Override public String message() { return "Category '" + id.value() + "' is still assigned to articles"; }
|
||||
}
|
||||
|
||||
record InvalidCategoryName(String reason) implements ProductCategoryError {
|
||||
@Override public String code() { return "CATEGORY_INVALID_NAME"; }
|
||||
@Override public String message() { return "Invalid category name: " + reason; }
|
||||
}
|
||||
|
||||
record Unauthorized(String message) implements ProductCategoryError {
|
||||
@Override public String code() { return "UNAUTHORIZED"; }
|
||||
}
|
||||
|
||||
record RepositoryFailure(String message) implements ProductCategoryError {
|
||||
@Override public String code() { return "REPOSITORY_ERROR"; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record ProductCategoryId(String value) {
|
||||
|
||||
public ProductCategoryId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("ProductCategoryId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static ProductCategoryId generate() {
|
||||
return new ProductCategoryId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static ProductCategoryId of(String value) {
|
||||
return new ProductCategoryId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.RepositoryError;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProductCategoryRepository {
|
||||
|
||||
Result<RepositoryError, Optional<ProductCategory>> findById(ProductCategoryId id);
|
||||
|
||||
Result<RepositoryError, List<ProductCategory>> findAll();
|
||||
|
||||
Result<RepositoryError, Void> save(ProductCategory category);
|
||||
|
||||
Result<RepositoryError, Void> delete(ProductCategory category);
|
||||
|
||||
Result<RepositoryError, Boolean> existsByName(CategoryName name);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Value Object representing a quality certificate held by a supplier.
|
||||
* Immutable, identified by content (record equality).
|
||||
*/
|
||||
public record QualityCertificate(
|
||||
String certificateType,
|
||||
String issuer,
|
||||
LocalDate validFrom,
|
||||
LocalDate validUntil
|
||||
) {
|
||||
|
||||
public QualityCertificate {
|
||||
if (certificateType == null || certificateType.isBlank()) {
|
||||
throw new IllegalArgumentException("Certificate type must not be blank");
|
||||
}
|
||||
if (validFrom != null && validUntil != null && validUntil.isBefore(validFrom)) {
|
||||
throw new IllegalArgumentException("validUntil must not be before validFrom");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return validUntil != null && validUntil.isBefore(LocalDate.now());
|
||||
}
|
||||
|
||||
public boolean expiresWithinDays(int days) {
|
||||
if (validUntil == null) return false;
|
||||
return !isExpired() && validUntil.isBefore(LocalDate.now().plusDays(days));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Money;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Entity representing a sales unit within an Article.
|
||||
* Enforces Unit/PriceModel consistency:
|
||||
* PIECE_FIXED → FIXED; KG/HUNDRED_GRAM/PIECE_VARIABLE → WEIGHT_BASED
|
||||
*/
|
||||
public class SalesUnit {
|
||||
|
||||
private final SalesUnitId id;
|
||||
private final Unit unit;
|
||||
private final PriceModel priceModel;
|
||||
private Money price;
|
||||
|
||||
private SalesUnit(SalesUnitId id, Unit unit, PriceModel priceModel, Money price) {
|
||||
this.id = id;
|
||||
this.unit = unit;
|
||||
this.priceModel = priceModel;
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public static Result<ArticleError, SalesUnit> create(Unit unit, PriceModel priceModel, Money price) {
|
||||
if (unit == null || priceModel == null) {
|
||||
return Result.failure(new ArticleError.InvalidPriceModelCombination(
|
||||
"Unit and PriceModel must not be null"));
|
||||
}
|
||||
if (!isValidCombination(unit, priceModel)) {
|
||||
return Result.failure(new ArticleError.InvalidPriceModelCombination(
|
||||
"Unit " + unit + " is not compatible with PriceModel " + priceModel));
|
||||
}
|
||||
if (price == null || price.amount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.failure(new ArticleError.InvalidPrice("Price must be positive"));
|
||||
}
|
||||
return Result.success(new SalesUnit(SalesUnitId.generate(), unit, priceModel, price));
|
||||
}
|
||||
|
||||
public static SalesUnit reconstitute(SalesUnitId id, Unit unit, PriceModel priceModel, Money price) {
|
||||
return new SalesUnit(id, unit, priceModel, price);
|
||||
}
|
||||
|
||||
public Result<ArticleError, Void> updatePrice(Money newPrice) {
|
||||
if (newPrice == null || newPrice.amount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.failure(new ArticleError.InvalidPrice("Price must be positive"));
|
||||
}
|
||||
this.price = newPrice;
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
private static boolean isValidCombination(Unit unit, PriceModel priceModel) {
|
||||
return switch (unit) {
|
||||
case PIECE_FIXED -> priceModel == PriceModel.FIXED;
|
||||
case KG, HUNDRED_GRAM, PIECE_VARIABLE -> priceModel == PriceModel.WEIGHT_BASED;
|
||||
};
|
||||
}
|
||||
|
||||
public SalesUnitId id() { return id; }
|
||||
public Unit unit() { return unit; }
|
||||
public PriceModel priceModel() { return priceModel; }
|
||||
public Money price() { return price; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof SalesUnit other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record SalesUnitId(String value) {
|
||||
|
||||
public SalesUnitId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("SalesUnitId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static SalesUnitId generate() {
|
||||
return new SalesUnitId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static SalesUnitId of(String value) {
|
||||
return new SalesUnitId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import de.effigenix.shared.common.Address;
|
||||
import de.effigenix.shared.common.ContactInfo;
|
||||
import de.effigenix.shared.common.PaymentTerms;
|
||||
import de.effigenix.shared.common.Result;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Aggregate Root for Supplier.
|
||||
*
|
||||
* Invariants:
|
||||
* 1. Name, Address, PaymentTerms non-null
|
||||
* 2. No structurally identical certificates (record equality)
|
||||
* 3. Rating scores 1-5 (enforced by SupplierRating VO)
|
||||
*/
|
||||
public class Supplier {
|
||||
|
||||
private final SupplierId id;
|
||||
private SupplierName name;
|
||||
private Address address;
|
||||
private ContactInfo contactInfo;
|
||||
private PaymentTerms paymentTerms;
|
||||
private final List<QualityCertificate> certificates;
|
||||
private SupplierRating rating;
|
||||
private SupplierStatus status;
|
||||
private final LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
private Supplier(
|
||||
SupplierId id,
|
||||
SupplierName name,
|
||||
Address address,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms,
|
||||
List<QualityCertificate> certificates,
|
||||
SupplierRating rating,
|
||||
SupplierStatus status,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.contactInfo = contactInfo;
|
||||
this.paymentTerms = paymentTerms;
|
||||
this.certificates = new ArrayList<>(certificates);
|
||||
this.rating = rating;
|
||||
this.status = status;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public static Result<SupplierError, Supplier> create(
|
||||
SupplierName name,
|
||||
Address address,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms
|
||||
) {
|
||||
if (name == null || address == null || paymentTerms == null) {
|
||||
return Result.failure(new SupplierError.SupplierNotFound(SupplierId.of("N/A")));
|
||||
}
|
||||
var now = LocalDateTime.now();
|
||||
return Result.success(new Supplier(
|
||||
SupplierId.generate(), name, address, contactInfo, paymentTerms,
|
||||
List.of(), null, SupplierStatus.ACTIVE, now, now
|
||||
));
|
||||
}
|
||||
|
||||
public static Supplier reconstitute(
|
||||
SupplierId id,
|
||||
SupplierName name,
|
||||
Address address,
|
||||
ContactInfo contactInfo,
|
||||
PaymentTerms paymentTerms,
|
||||
List<QualityCertificate> certificates,
|
||||
SupplierRating rating,
|
||||
SupplierStatus status,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
return new Supplier(id, name, address, contactInfo, paymentTerms,
|
||||
certificates, rating, status, createdAt, updatedAt);
|
||||
}
|
||||
|
||||
// ==================== Business Methods ====================
|
||||
|
||||
public void updateName(SupplierName newName) {
|
||||
this.name = newName;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updateAddress(Address newAddress) {
|
||||
this.address = newAddress;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updateContactInfo(ContactInfo newContactInfo) {
|
||||
this.contactInfo = newContactInfo;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void updatePaymentTerms(PaymentTerms newPaymentTerms) {
|
||||
this.paymentTerms = newPaymentTerms;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void addCertificate(QualityCertificate certificate) {
|
||||
if (!this.certificates.contains(certificate)) {
|
||||
this.certificates.add(certificate);
|
||||
touch();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeCertificate(QualityCertificate certificate) {
|
||||
this.certificates.remove(certificate);
|
||||
touch();
|
||||
}
|
||||
|
||||
public void rate(SupplierRating newRating) {
|
||||
this.rating = newRating;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
this.status = SupplierStatus.ACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
this.status = SupplierStatus.INACTIVE;
|
||||
touch();
|
||||
}
|
||||
|
||||
public List<QualityCertificate> expiredCertificates() {
|
||||
return certificates.stream()
|
||||
.filter(QualityCertificate::isExpired)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<QualityCertificate> certificatesExpiringSoon(int days) {
|
||||
return certificates.stream()
|
||||
.filter(c -> c.expiresWithinDays(days))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// ==================== Helpers ====================
|
||||
|
||||
private void touch() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// ==================== Getters ====================
|
||||
|
||||
public SupplierId id() { return id; }
|
||||
public SupplierName name() { return name; }
|
||||
public Address address() { return address; }
|
||||
public ContactInfo contactInfo() { return contactInfo; }
|
||||
public PaymentTerms paymentTerms() { return paymentTerms; }
|
||||
public List<QualityCertificate> certificates() { return Collections.unmodifiableList(certificates); }
|
||||
public SupplierRating rating() { return rating; }
|
||||
public SupplierStatus status() { return status; }
|
||||
public LocalDateTime createdAt() { return createdAt; }
|
||||
public LocalDateTime updatedAt() { return updatedAt; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Supplier other)) return false;
|
||||
return id.equals(other.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Supplier{id=" + id + ", name=" + name + ", status=" + status + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public sealed interface SupplierError {
|
||||
|
||||
String code();
|
||||
String message();
|
||||
|
||||
record SupplierNotFound(SupplierId id) implements SupplierError {
|
||||
@Override public String code() { return "SUPPLIER_NOT_FOUND"; }
|
||||
@Override public String message() { return "Supplier with ID '" + id.value() + "' not found"; }
|
||||
}
|
||||
|
||||
record SupplierNameAlreadyExists(String name) implements SupplierError {
|
||||
@Override public String code() { return "SUPPLIER_NAME_EXISTS"; }
|
||||
@Override public String message() { return "Supplier name '" + name + "' already exists"; }
|
||||
}
|
||||
|
||||
record InvalidRating(String reason) implements SupplierError {
|
||||
@Override public String code() { return "INVALID_RATING"; }
|
||||
@Override public String message() { return "Invalid rating: " + reason; }
|
||||
}
|
||||
|
||||
record Unauthorized(String message) implements SupplierError {
|
||||
@Override public String code() { return "UNAUTHORIZED"; }
|
||||
}
|
||||
|
||||
record RepositoryFailure(String message) implements SupplierError {
|
||||
@Override public String code() { return "REPOSITORY_ERROR"; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record SupplierId(String value) {
|
||||
|
||||
public SupplierId {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("SupplierId must not be blank");
|
||||
}
|
||||
}
|
||||
|
||||
public static SupplierId generate() {
|
||||
return new SupplierId(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public static SupplierId of(String value) {
|
||||
return new SupplierId(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
public record SupplierName(String value) {
|
||||
|
||||
public SupplierName {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("SupplierName must not be blank");
|
||||
}
|
||||
if (value.length() > 200) {
|
||||
throw new IllegalArgumentException("SupplierName must not exceed 200 characters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package de.effigenix.domain.masterdata;
|
||||
|
||||
/**
|
||||
* Value Object representing a supplier rating with three scores (1-5).
|
||||
*/
|
||||
public record SupplierRating(int qualityScore, int deliveryScore, int priceScore) {
|
||||
|
||||
public SupplierRating {
|
||||
validateScore("qualityScore", qualityScore);
|
||||
validateScore("deliveryScore", deliveryScore);
|
||||
validateScore("priceScore", priceScore);
|
||||
}
|
||||
|
||||
public double averageScore() {
|
||||
return (qualityScore + deliveryScore + priceScore) / 3.0;
|
||||
}
|
||||
|
||||
private static void validateScore(String name, int score) {
|
||||
if (score < 1 || score > 5) {
|
||||
throw new IllegalArgumentException(name + " must be between 1 and 5, was: " + score);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue