From b46495e1aa7683adbe2083636d8ba9947ca034ae Mon Sep 17 00:00:00 2001 From: Sebastian Frick Date: Fri, 20 Feb 2026 00:40:58 +0100 Subject: [PATCH] refactor: OffsetDateTime-Migration, atomare Batch-Sequenznummern und Quantity.reconstitute-Overload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LocalDateTime → OffsetDateTime (UTC) in allen Domain-Klassen, JPA Entities, DTOs und Tests - Liquibase-Migration 017: TIMESTAMP → TIMESTAMP WITH TIME ZONE für bestehende Spalten - Custom DateTimeProvider für Spring Data @CreatedDate-Kompatibilität mit OffsetDateTime - Neue Sequenztabelle (016) mit JPA Entity + PESSIMISTIC_WRITE Lock statt COUNT-basierter Batch-Nummernvergabe (Race Condition Fix) - Quantity.reconstitute(amount, uom) 2-Parameter-Overload für bessere Lesbarkeit --- .../usermanagement/AuthenticateUser.java | 5 ++- .../usermanagement/dto/SessionToken.java | 7 ++-- .../usermanagement/dto/UserDTO.java | 6 +-- .../domain/inventory/MinimumLevel.java | 2 +- .../domain/inventory/StockBatch.java | 2 +- .../effigenix/domain/masterdata/Article.java | 23 ++++++------ .../effigenix/domain/masterdata/Customer.java | 23 ++++++------ .../effigenix/domain/masterdata/Supplier.java | 23 ++++++------ .../de/effigenix/domain/production/Batch.java | 21 ++++++----- .../effigenix/domain/production/Recipe.java | 23 ++++++------ .../effigenix/domain/usermanagement/User.java | 25 +++++++------ .../infrastructure/audit/AuditLogEntity.java | 15 ++++---- .../audit/AuditLogJpaRepository.java | 4 +- .../audit/DatabaseAuditLogger.java | 11 +++--- .../config/JpaAuditingConfig.java | 14 ++++++- .../persistence/mapper/StockMapper.java | 6 +-- .../persistence/entity/ArticleEntity.java | 16 ++++---- .../persistence/entity/CustomerEntity.java | 14 +++---- .../persistence/entity/SupplierEntity.java | 14 +++---- .../masterdata/web/dto/ArticleResponse.java | 6 +-- .../masterdata/web/dto/CustomerResponse.java | 6 +-- .../masterdata/web/dto/SupplierResponse.java | 6 +-- .../persistence/JpaBatchNumberGenerator.java | 22 ++++++++--- .../persistence/entity/BatchEntity.java | 14 +++---- .../entity/BatchNumberSequenceEntity.java | 37 +++++++++++++++++++ .../persistence/entity/RecipeEntity.java | 16 ++++---- .../persistence/mapper/BatchMapper.java | 3 +- .../persistence/mapper/RecipeMapper.java | 6 +-- .../repository/BatchJpaRepository.java | 6 --- .../BatchNumberSequenceJpaRepository.java | 15 ++++++++ .../production/web/dto/BatchResponse.java | 6 +-- .../production/web/dto/RecipeResponse.java | 6 +-- .../web/dto/RecipeSummaryResponse.java | 6 +-- .../persistence/entity/UserEntity.java | 18 ++++----- .../usermanagement/web/dto/ErrorResponse.java | 9 +++-- .../usermanagement/web/dto/LoginResponse.java | 4 +- .../de/effigenix/shared/common/Quantity.java | 9 ++++- ...16-create-batch-number-sequences-table.xml | 19 ++++++++++ .../changes/017-timestamps-to-timestamptz.xml | 36 ++++++++++++++++++ .../db/changelog/db.changelog-master.xml | 2 + .../inventory/AddStockBatchTest.java | 2 +- .../inventory/RemoveStockBatchTest.java | 2 +- .../production/ActivateRecipeTest.java | 5 ++- .../production/ArchiveRecipeTest.java | 7 ++-- .../application/production/GetRecipeTest.java | 5 ++- .../production/ListRecipesTest.java | 5 ++- .../application/production/PlanBatchTest.java | 7 ++-- .../production/RecipeCycleCheckerTest.java | 7 ++-- .../usermanagement/AssignRoleTest.java | 5 ++- .../usermanagement/AuthenticateUserTest.java | 13 ++++--- .../usermanagement/ChangePasswordTest.java | 5 ++- .../usermanagement/GetUserTest.java | 5 ++- .../usermanagement/ListUsersTest.java | 7 ++-- .../usermanagement/LockUserTest.java | 9 +++-- .../usermanagement/RemoveRoleTest.java | 5 ++- .../usermanagement/UnlockUserTest.java | 9 +++-- .../usermanagement/UpdateUserTest.java | 5 ++- .../effigenix/domain/inventory/StockTest.java | 4 +- .../domain/production/BatchTest.java | 7 ++-- .../domain/production/RecipeTest.java | 16 ++++---- .../domain/usermanagement/UserTest.java | 13 ++++--- .../AbstractIntegrationTest.java | 5 ++- .../persistence/mapper/UserMapperTest.java | 11 +++--- .../web/SecurityIntegrationTest.java | 5 ++- 64 files changed, 414 insertions(+), 256 deletions(-) create mode 100644 backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchNumberSequenceEntity.java create mode 100644 backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchNumberSequenceJpaRepository.java create mode 100644 backend/src/main/resources/db/changelog/changes/016-create-batch-number-sequences-table.xml create mode 100644 backend/src/main/resources/db/changelog/changes/017-timestamps-to-timestamptz.xml diff --git a/backend/src/main/java/de/effigenix/application/usermanagement/AuthenticateUser.java b/backend/src/main/java/de/effigenix/application/usermanagement/AuthenticateUser.java index 2396056..5cf48a6 100644 --- a/backend/src/main/java/de/effigenix/application/usermanagement/AuthenticateUser.java +++ b/backend/src/main/java/de/effigenix/application/usermanagement/AuthenticateUser.java @@ -7,7 +7,8 @@ import de.effigenix.shared.common.Result; import de.effigenix.shared.security.ActorId; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.Optional; /** @@ -71,7 +72,7 @@ public class AuthenticateUser { SessionToken token = sessionManager.createSession(user); // 5. Update last login timestamp (immutable) - return user.withLastLogin(LocalDateTime.now()) + return user.withLastLogin(OffsetDateTime.now(ZoneOffset.UTC)) .flatMap(updated -> userRepository.save(updated) .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message())) .map(ignored -> { diff --git a/backend/src/main/java/de/effigenix/application/usermanagement/dto/SessionToken.java b/backend/src/main/java/de/effigenix/application/usermanagement/dto/SessionToken.java index 1c523f1..fd1489c 100644 --- a/backend/src/main/java/de/effigenix/application/usermanagement/dto/SessionToken.java +++ b/backend/src/main/java/de/effigenix/application/usermanagement/dto/SessionToken.java @@ -1,6 +1,7 @@ package de.effigenix.application.usermanagement.dto; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; /** * JWT session token returned after successful authentication. @@ -9,7 +10,7 @@ public record SessionToken( String accessToken, String tokenType, long expiresIn, // in seconds - LocalDateTime expiresAt, + OffsetDateTime expiresAt, String refreshToken // for future refresh token support ) { public static SessionToken create(String accessToken, long expiresInMs, String refreshToken) { @@ -17,7 +18,7 @@ public record SessionToken( accessToken, "Bearer", expiresInMs / 1000, // convert to seconds - LocalDateTime.now().plusSeconds(expiresInMs / 1000), + OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(expiresInMs / 1000), refreshToken ); } diff --git a/backend/src/main/java/de/effigenix/application/usermanagement/dto/UserDTO.java b/backend/src/main/java/de/effigenix/application/usermanagement/dto/UserDTO.java index 3b39bbf..44d67de 100644 --- a/backend/src/main/java/de/effigenix/application/usermanagement/dto/UserDTO.java +++ b/backend/src/main/java/de/effigenix/application/usermanagement/dto/UserDTO.java @@ -3,7 +3,7 @@ package de.effigenix.application.usermanagement.dto; import de.effigenix.domain.usermanagement.User; import de.effigenix.domain.usermanagement.UserStatus; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.Set; import java.util.stream.Collectors; @@ -18,8 +18,8 @@ public record UserDTO( Set roles, String branchId, UserStatus status, - LocalDateTime createdAt, - LocalDateTime lastLogin + OffsetDateTime createdAt, + OffsetDateTime lastLogin ) { /** * Maps a User entity to a UserDTO. diff --git a/backend/src/main/java/de/effigenix/domain/inventory/MinimumLevel.java b/backend/src/main/java/de/effigenix/domain/inventory/MinimumLevel.java index f96ba63..cf6fa02 100644 --- a/backend/src/main/java/de/effigenix/domain/inventory/MinimumLevel.java +++ b/backend/src/main/java/de/effigenix/domain/inventory/MinimumLevel.java @@ -37,7 +37,7 @@ public record MinimumLevel(Quantity quantity) { // MinimumLevel erlaubt amount == 0 (kein Mindestbestand) // Quantity.of() verlangt amount > 0, daher Reconstitute verwenden if (parsedAmount.compareTo(BigDecimal.ZERO) == 0) { - return Result.success(new MinimumLevel(Quantity.reconstitute(parsedAmount, parsedUnit, null, null))); + return Result.success(new MinimumLevel(Quantity.reconstitute(parsedAmount, parsedUnit))); } return Quantity.of(parsedAmount, parsedUnit) diff --git a/backend/src/main/java/de/effigenix/domain/inventory/StockBatch.java b/backend/src/main/java/de/effigenix/domain/inventory/StockBatch.java index a8b510b..f9ee705 100644 --- a/backend/src/main/java/de/effigenix/domain/inventory/StockBatch.java +++ b/backend/src/main/java/de/effigenix/domain/inventory/StockBatch.java @@ -108,7 +108,7 @@ public class StockBatch { return Result.failure(new StockError.NegativeStockNotAllowed()); } if (remaining.compareTo(BigDecimal.ZERO) == 0) { - return Result.success(Quantity.reconstitute(BigDecimal.ZERO, this.quantity.uom(), null, null)); + return Result.success(Quantity.reconstitute(BigDecimal.ZERO, this.quantity.uom())); } switch (Quantity.of(remaining, this.quantity.uom())) { case Result.Failure(var err) -> { diff --git a/backend/src/main/java/de/effigenix/domain/masterdata/Article.java b/backend/src/main/java/de/effigenix/domain/masterdata/Article.java index 6c04199..1995018 100644 --- a/backend/src/main/java/de/effigenix/domain/masterdata/Article.java +++ b/backend/src/main/java/de/effigenix/domain/masterdata/Article.java @@ -5,7 +5,8 @@ import de.effigenix.shared.common.Result; import static de.effigenix.shared.common.Result.*; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.*; /** @@ -26,8 +27,8 @@ public class Article { private final List salesUnits; private ArticleStatus status; private final Set supplierReferences; - private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final OffsetDateTime createdAt; + private OffsetDateTime updatedAt; private Article( ArticleId id, @@ -37,8 +38,8 @@ public class Article { List salesUnits, ArticleStatus status, Set supplierReferences, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.name = name; @@ -88,7 +89,7 @@ public class Article { case Success(var val) -> salesUnit = val; } - var now = LocalDateTime.now(); + var now = OffsetDateTime.now(ZoneOffset.UTC); return Result.success(new Article( ArticleId.generate(), name, @@ -110,8 +111,8 @@ public class Article { List salesUnits, ArticleStatus status, Set supplierReferences, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { return new Article(id, name, articleNumber, categoryId, salesUnits, status, supplierReferences, createdAt, updatedAt); @@ -208,7 +209,7 @@ public class Article { } private void touch() { - this.updatedAt = LocalDateTime.now(); + this.updatedAt = OffsetDateTime.now(ZoneOffset.UTC); } // ==================== Getters ==================== @@ -220,8 +221,8 @@ public class Article { public List salesUnits() { return Collections.unmodifiableList(salesUnits); } public ArticleStatus status() { return status; } public Set supplierReferences() { return Collections.unmodifiableSet(supplierReferences); } - public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime updatedAt() { return updatedAt; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime updatedAt() { return updatedAt; } @Override public boolean equals(Object obj) { diff --git a/backend/src/main/java/de/effigenix/domain/masterdata/Customer.java b/backend/src/main/java/de/effigenix/domain/masterdata/Customer.java index b930761..a17ced9 100644 --- a/backend/src/main/java/de/effigenix/domain/masterdata/Customer.java +++ b/backend/src/main/java/de/effigenix/domain/masterdata/Customer.java @@ -8,7 +8,8 @@ import de.effigenix.shared.common.Result; import static de.effigenix.shared.common.Result.*; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.*; /** @@ -33,8 +34,8 @@ public class Customer { private FrameContract frameContract; private final Set preferences; private CustomerStatus status; - private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final OffsetDateTime createdAt; + private OffsetDateTime updatedAt; private Customer( CustomerId id, @@ -47,8 +48,8 @@ public class Customer { FrameContract frameContract, Set preferences, CustomerStatus status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.name = name; @@ -100,7 +101,7 @@ public class Customer { } } - var now = LocalDateTime.now(); + var now = OffsetDateTime.now(ZoneOffset.UTC); return Result.success(new Customer( CustomerId.generate(), name, draft.type(), billingAddress, contactInfo, paymentTerms, List.of(), null, Set.of(), CustomerStatus.ACTIVE, now, now @@ -118,8 +119,8 @@ public class Customer { FrameContract frameContract, Set preferences, CustomerStatus status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { return new Customer(id, name, type, billingAddress, contactInfo, paymentTerms, deliveryAddresses, frameContract, preferences, status, createdAt, updatedAt); @@ -235,7 +236,7 @@ public class Customer { // ==================== Helpers ==================== private void touch() { - this.updatedAt = LocalDateTime.now(); + this.updatedAt = OffsetDateTime.now(ZoneOffset.UTC); } // ==================== Getters ==================== @@ -250,8 +251,8 @@ public class Customer { public FrameContract frameContract() { return frameContract; } public Set preferences() { return Collections.unmodifiableSet(preferences); } public CustomerStatus status() { return status; } - public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime updatedAt() { return updatedAt; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime updatedAt() { return updatedAt; } @Override public boolean equals(Object obj) { diff --git a/backend/src/main/java/de/effigenix/domain/masterdata/Supplier.java b/backend/src/main/java/de/effigenix/domain/masterdata/Supplier.java index 75946cb..dbcc856 100644 --- a/backend/src/main/java/de/effigenix/domain/masterdata/Supplier.java +++ b/backend/src/main/java/de/effigenix/domain/masterdata/Supplier.java @@ -5,7 +5,8 @@ import de.effigenix.shared.common.Address; import de.effigenix.shared.common.ContactInfo; import de.effigenix.shared.common.PaymentTerms; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -31,8 +32,8 @@ public class Supplier { private final List certificates; private SupplierRating rating; private SupplierStatus status; - private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final OffsetDateTime createdAt; + private OffsetDateTime updatedAt; private Supplier( SupplierId id, @@ -43,8 +44,8 @@ public class Supplier { List certificates, SupplierRating rating, SupplierStatus status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.name = name; @@ -96,7 +97,7 @@ public class Supplier { } } - var now = LocalDateTime.now(); + var now = OffsetDateTime.now(ZoneOffset.UTC); return Result.success(new Supplier( SupplierId.generate(), name, address, contactInfo, paymentTerms, List.of(), null, SupplierStatus.ACTIVE, now, now @@ -112,8 +113,8 @@ public class Supplier { List certificates, SupplierRating rating, SupplierStatus status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { return new Supplier(id, name, address, contactInfo, paymentTerms, certificates, rating, status, createdAt, updatedAt); @@ -200,7 +201,7 @@ public class Supplier { // ==================== Helpers ==================== private void touch() { - this.updatedAt = LocalDateTime.now(); + this.updatedAt = OffsetDateTime.now(ZoneOffset.UTC); } // ==================== Getters ==================== @@ -213,8 +214,8 @@ public class Supplier { public List 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; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime updatedAt() { return updatedAt; } @Override public boolean equals(Object obj) { diff --git a/backend/src/main/java/de/effigenix/domain/production/Batch.java b/backend/src/main/java/de/effigenix/domain/production/Batch.java index 30eabde..368318b 100644 --- a/backend/src/main/java/de/effigenix/domain/production/Batch.java +++ b/backend/src/main/java/de/effigenix/domain/production/Batch.java @@ -6,7 +6,8 @@ import de.effigenix.shared.common.UnitOfMeasure; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; /** * Batch aggregate root. @@ -27,8 +28,8 @@ public class Batch { private final Quantity plannedQuantity; private final LocalDate productionDate; private final LocalDate bestBeforeDate; - private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final OffsetDateTime createdAt; + private OffsetDateTime updatedAt; private Batch( BatchId id, @@ -38,8 +39,8 @@ public class Batch { Quantity plannedQuantity, LocalDate productionDate, LocalDate bestBeforeDate, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.batchNumber = batchNumber; @@ -88,7 +89,7 @@ public class Batch { "Invalid unit: " + draft.plannedQuantityUnit())); } - var now = LocalDateTime.now(); + var now = OffsetDateTime.now(ZoneOffset.UTC); return Result.success(new Batch( BatchId.generate(), batchNumber, @@ -110,8 +111,8 @@ public class Batch { Quantity plannedQuantity, LocalDate productionDate, LocalDate bestBeforeDate, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { return new Batch(id, batchNumber, recipeId, status, plannedQuantity, productionDate, bestBeforeDate, createdAt, updatedAt); } @@ -123,6 +124,6 @@ public class Batch { public Quantity plannedQuantity() { return plannedQuantity; } public LocalDate productionDate() { return productionDate; } public LocalDate bestBeforeDate() { return bestBeforeDate; } - public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime updatedAt() { return updatedAt; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime updatedAt() { return updatedAt; } } diff --git a/backend/src/main/java/de/effigenix/domain/production/Recipe.java b/backend/src/main/java/de/effigenix/domain/production/Recipe.java index c6c6c35..b39d15d 100644 --- a/backend/src/main/java/de/effigenix/domain/production/Recipe.java +++ b/backend/src/main/java/de/effigenix/domain/production/Recipe.java @@ -5,7 +5,8 @@ import de.effigenix.shared.common.Result; import de.effigenix.shared.common.UnitOfMeasure; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -44,8 +45,8 @@ public class Recipe { private RecipeStatus status; private final List ingredients; private final List productionSteps; - private final LocalDateTime createdAt; - private LocalDateTime updatedAt; + private final OffsetDateTime createdAt; + private OffsetDateTime updatedAt; private Recipe( RecipeId id, @@ -59,8 +60,8 @@ public class Recipe { RecipeStatus status, List ingredients, List productionSteps, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.name = name; @@ -123,7 +124,7 @@ public class Recipe { return Result.failure(new RecipeError.ValidationFailure("Invalid output quantity: " + e.getMessage())); } - var now = LocalDateTime.now(); + var now = OffsetDateTime.now(ZoneOffset.UTC); return Result.success(new Recipe( RecipeId.generate(), name, draft.version(), draft.type(), draft.description(), yieldPercentage, shelfLifeDays, outputQuantity, @@ -146,8 +147,8 @@ public class Recipe { RecipeStatus status, List ingredients, List productionSteps, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { return new Recipe(id, name, version, type, description, yieldPercentage, shelfLifeDays, outputQuantity, status, ingredients, productionSteps, createdAt, updatedAt); @@ -255,8 +256,8 @@ public class Recipe { public RecipeStatus status() { return status; } public List ingredients() { return Collections.unmodifiableList(ingredients); } public List productionSteps() { return Collections.unmodifiableList(productionSteps); } - public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime updatedAt() { return updatedAt; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime updatedAt() { return updatedAt; } // ==================== Helpers ==================== @@ -269,7 +270,7 @@ public class Recipe { } private void touch() { - this.updatedAt = LocalDateTime.now(); + this.updatedAt = OffsetDateTime.now(ZoneOffset.UTC); } @Override diff --git a/backend/src/main/java/de/effigenix/domain/usermanagement/User.java b/backend/src/main/java/de/effigenix/domain/usermanagement/User.java index 3dbd30b..c3284b7 100644 --- a/backend/src/main/java/de/effigenix/domain/usermanagement/User.java +++ b/backend/src/main/java/de/effigenix/domain/usermanagement/User.java @@ -2,7 +2,8 @@ package de.effigenix.domain.usermanagement; import de.effigenix.shared.common.Result; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -33,8 +34,8 @@ public class User { private final Set roles; private final String branchId; private final UserStatus status; - private final LocalDateTime createdAt; - private final LocalDateTime lastLogin; + private final OffsetDateTime createdAt; + private final OffsetDateTime lastLogin; private User( UserId id, @@ -44,8 +45,8 @@ public class User { Set roles, String branchId, UserStatus status, - LocalDateTime createdAt, - LocalDateTime lastLogin + OffsetDateTime createdAt, + OffsetDateTime lastLogin ) { this.id = id; this.username = username; @@ -54,7 +55,7 @@ public class User { this.roles = roles != null ? Set.copyOf(roles) : Set.of(); this.branchId = branchId; this.status = status; - this.createdAt = createdAt != null ? createdAt : LocalDateTime.now(); + this.createdAt = createdAt != null ? createdAt : OffsetDateTime.now(ZoneOffset.UTC); this.lastLogin = lastLogin; } @@ -86,7 +87,7 @@ public class User { roles, branchId, UserStatus.ACTIVE, - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), null )); } @@ -102,15 +103,15 @@ public class User { Set roles, String branchId, UserStatus status, - LocalDateTime createdAt, - LocalDateTime lastLogin + OffsetDateTime createdAt, + OffsetDateTime lastLogin ) { return new User(id, username, email, passwordHash, roles, branchId, status, createdAt, lastLogin); } // ==================== Business Methods (Wither-Pattern) ==================== - public Result withLastLogin(LocalDateTime timestamp) { + public Result withLastLogin(OffsetDateTime timestamp) { return Result.success(new User(id, username, email, passwordHash, roles, branchId, status, createdAt, timestamp)); } @@ -218,8 +219,8 @@ public class User { public Set roles() { return Collections.unmodifiableSet(roles); } public String branchId() { return branchId; } public UserStatus status() { return status; } - public LocalDateTime createdAt() { return createdAt; } - public LocalDateTime lastLogin() { return lastLogin; } + public OffsetDateTime createdAt() { return createdAt; } + public OffsetDateTime lastLogin() { return lastLogin; } @Override public boolean equals(Object obj) { diff --git a/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogEntity.java b/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogEntity.java index e526c08..7173a60 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogEntity.java @@ -5,7 +5,8 @@ import jakarta.persistence.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; /** * JPA Entity for Audit Logs. @@ -43,7 +44,7 @@ public class AuditLogEntity { private String details; @Column(name = "timestamp", nullable = false) - private LocalDateTime timestamp; + private OffsetDateTime timestamp; @Column(name = "ip_address", length = 45) // IPv6 max length private String ipAddress; @@ -53,7 +54,7 @@ public class AuditLogEntity { @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; // JPA requires no-arg constructor protected AuditLogEntity() { @@ -65,7 +66,7 @@ public class AuditLogEntity { String entityId, String performedBy, String details, - LocalDateTime timestamp, + OffsetDateTime timestamp, String ipAddress, String userAgent ) { @@ -77,7 +78,7 @@ public class AuditLogEntity { this.timestamp = timestamp; this.ipAddress = ipAddress; this.userAgent = userAgent; - this.createdAt = LocalDateTime.now(); + this.createdAt = OffsetDateTime.now(ZoneOffset.UTC); } // Getters only (immutable after creation) @@ -101,7 +102,7 @@ public class AuditLogEntity { return details; } - public LocalDateTime getTimestamp() { + public OffsetDateTime getTimestamp() { return timestamp; } @@ -113,7 +114,7 @@ public class AuditLogEntity { return userAgent; } - public LocalDateTime getCreatedAt() { + public OffsetDateTime getCreatedAt() { return createdAt; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogJpaRepository.java b/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogJpaRepository.java index 76a8374..feb6b74 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogJpaRepository.java +++ b/backend/src/main/java/de/effigenix/infrastructure/audit/AuditLogJpaRepository.java @@ -4,7 +4,7 @@ import de.effigenix.application.usermanagement.AuditEvent; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; /** @@ -34,7 +34,7 @@ public interface AuditLogJpaRepository extends JpaRepository findByTimestampBetween(LocalDateTime start, LocalDateTime end); + List findByTimestampBetween(OffsetDateTime start, OffsetDateTime end); /** * Finds all audit logs for a specific event and actor. diff --git a/backend/src/main/java/de/effigenix/infrastructure/audit/DatabaseAuditLogger.java b/backend/src/main/java/de/effigenix/infrastructure/audit/DatabaseAuditLogger.java index bc74709..efee0d8 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/audit/DatabaseAuditLogger.java +++ b/backend/src/main/java/de/effigenix/infrastructure/audit/DatabaseAuditLogger.java @@ -13,7 +13,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.UUID; /** @@ -52,7 +53,7 @@ public class DatabaseAuditLogger implements AuditLogger { entityId, performedBy.value(), null, // no additional details - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), getClientIpAddress(), getUserAgent() ); @@ -75,7 +76,7 @@ public class DatabaseAuditLogger implements AuditLogger { null, // no entity ID null, // no actor (e.g., system event) details, - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), getClientIpAddress(), getUserAgent() ); @@ -97,7 +98,7 @@ public class DatabaseAuditLogger implements AuditLogger { entityId, performedBy.value(), details, - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), getClientIpAddress(), getUserAgent() ); @@ -119,7 +120,7 @@ public class DatabaseAuditLogger implements AuditLogger { null, // no entity ID performedBy.value(), null, // no additional details - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), getClientIpAddress(), getUserAgent() ); diff --git a/backend/src/main/java/de/effigenix/infrastructure/config/JpaAuditingConfig.java b/backend/src/main/java/de/effigenix/infrastructure/config/JpaAuditingConfig.java index ff165e2..aae8e32 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/config/JpaAuditingConfig.java +++ b/backend/src/main/java/de/effigenix/infrastructure/config/JpaAuditingConfig.java @@ -1,14 +1,26 @@ package de.effigenix.infrastructure.config; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Optional; + /** * Aktiviert JPA-Auditing nur, wenn eine Datenbankverbindung vorhanden ist. + * Verwendet OffsetDateTime (UTC) statt LocalDateTime für @CreatedDate/@LastModifiedDate. */ @Configuration @Profile("!no-db") -@EnableJpaAuditing +@EnableJpaAuditing(dateTimeProviderRef = "utcDateTimeProvider") public class JpaAuditingConfig { + + @Bean + public DateTimeProvider utcDateTimeProvider() { + return () -> Optional.of(OffsetDateTime.now(ZoneOffset.UTC)); + } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/inventory/persistence/mapper/StockMapper.java b/backend/src/main/java/de/effigenix/infrastructure/inventory/persistence/mapper/StockMapper.java index 5352707..4f7c49e 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/inventory/persistence/mapper/StockMapper.java +++ b/backend/src/main/java/de/effigenix/infrastructure/inventory/persistence/mapper/StockMapper.java @@ -42,8 +42,7 @@ public class StockMapper { if (entity.getMinimumLevelAmount() != null && entity.getMinimumLevelUnit() != null) { var quantity = Quantity.reconstitute( entity.getMinimumLevelAmount(), - UnitOfMeasure.valueOf(entity.getMinimumLevelUnit()), - null, null + UnitOfMeasure.valueOf(entity.getMinimumLevelUnit()) ); minimumLevel = new MinimumLevel(quantity); } @@ -87,8 +86,7 @@ public class StockMapper { new BatchReference(entity.getBatchId(), BatchType.valueOf(entity.getBatchType())), Quantity.reconstitute( entity.getQuantityAmount(), - UnitOfMeasure.valueOf(entity.getQuantityUnit()), - null, null + UnitOfMeasure.valueOf(entity.getQuantityUnit()) ), entity.getExpiryDate(), StockBatchStatus.valueOf(entity.getStatus()), diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/ArticleEntity.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/ArticleEntity.java index ba89c70..39219e3 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/ArticleEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/ArticleEntity.java @@ -2,7 +2,7 @@ package de.effigenix.infrastructure.masterdata.persistence.entity; import jakarta.persistence.*; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -29,10 +29,10 @@ public class ArticleEntity { private String status; @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; + private OffsetDateTime updatedAt; @OneToMany(mappedBy = "article", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private List salesUnits = new ArrayList<>(); @@ -45,7 +45,7 @@ public class ArticleEntity { protected ArticleEntity() {} public ArticleEntity(String id, String name, String articleNumber, String categoryId, - String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + String status, OffsetDateTime createdAt, OffsetDateTime updatedAt) { this.id = id; this.name = name; this.articleNumber = articleNumber; @@ -60,8 +60,8 @@ public class ArticleEntity { public String getArticleNumber() { return articleNumber; } public String getCategoryId() { return categoryId; } public String getStatus() { return status; } - public LocalDateTime getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } + public OffsetDateTime getCreatedAt() { return createdAt; } + public OffsetDateTime getUpdatedAt() { return updatedAt; } public List getSalesUnits() { return salesUnits; } public Set getSupplierIds() { return supplierIds; } @@ -70,8 +70,8 @@ public class ArticleEntity { public void setArticleNumber(String articleNumber) { this.articleNumber = articleNumber; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public void setStatus(String status) { this.status = status; } - public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; } + public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } public void setSalesUnits(List salesUnits) { this.salesUnits = salesUnits; } public void setSupplierIds(Set supplierIds) { this.supplierIds = supplierIds; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/CustomerEntity.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/CustomerEntity.java index b22a4bf..ff67acf 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/CustomerEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/CustomerEntity.java @@ -2,7 +2,7 @@ package de.effigenix.infrastructure.masterdata.persistence.entity; import jakarta.persistence.*; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -56,10 +56,10 @@ public class CustomerEntity { private String status; @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; + private OffsetDateTime updatedAt; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "delivery_addresses", joinColumns = @JoinColumn(name = "customer_id")) @@ -91,8 +91,8 @@ public class CustomerEntity { public Integer getPaymentDueDays() { return paymentDueDays; } public String getPaymentDescription() { return paymentDescription; } public String getStatus() { return status; } - public LocalDateTime getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } + public OffsetDateTime getCreatedAt() { return createdAt; } + public OffsetDateTime getUpdatedAt() { return updatedAt; } public List getDeliveryAddresses() { return deliveryAddresses; } public Set getPreferences() { return preferences; } public FrameContractEntity getFrameContract() { return frameContract; } @@ -111,8 +111,8 @@ public class CustomerEntity { public void setPaymentDueDays(Integer paymentDueDays) { this.paymentDueDays = paymentDueDays; } public void setPaymentDescription(String paymentDescription) { this.paymentDescription = paymentDescription; } public void setStatus(String status) { this.status = status; } - public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; } + public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } public void setDeliveryAddresses(List deliveryAddresses) { this.deliveryAddresses = deliveryAddresses; } public void setPreferences(Set preferences) { this.preferences = preferences; } public void setFrameContract(FrameContractEntity frameContract) { this.frameContract = frameContract; } diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/SupplierEntity.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/SupplierEntity.java index 918bb88..3fcc73a 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/SupplierEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/persistence/entity/SupplierEntity.java @@ -2,7 +2,7 @@ package de.effigenix.infrastructure.masterdata.persistence.entity; import jakarta.persistence.*; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; @@ -60,10 +60,10 @@ public class SupplierEntity { private String status; @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; + private OffsetDateTime updatedAt; @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "quality_certificates", joinColumns = @JoinColumn(name = "supplier_id")) @@ -87,8 +87,8 @@ public class SupplierEntity { public Integer getDeliveryScore() { return deliveryScore; } public Integer getPriceScore() { return priceScore; } public String getStatus() { return status; } - public LocalDateTime getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } + public OffsetDateTime getCreatedAt() { return createdAt; } + public OffsetDateTime getUpdatedAt() { return updatedAt; } public List getCertificates() { return certificates; } public void setId(String id) { this.id = id; } @@ -107,7 +107,7 @@ public class SupplierEntity { public void setDeliveryScore(Integer deliveryScore) { this.deliveryScore = deliveryScore; } public void setPriceScore(Integer priceScore) { this.priceScore = priceScore; } public void setStatus(String status) { this.status = status; } - public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; } + public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } public void setCertificates(List certificates) { this.certificates = certificates; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/ArticleResponse.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/ArticleResponse.java index 12e3c56..57898b5 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/ArticleResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/ArticleResponse.java @@ -4,7 +4,7 @@ import de.effigenix.domain.masterdata.Article; import de.effigenix.domain.masterdata.SupplierId; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; @Schema(requiredProperties = {"id", "name", "articleNumber", "categoryId", "salesUnits", "status", "supplierIds", "createdAt", "updatedAt"}) @@ -16,8 +16,8 @@ public record ArticleResponse( List salesUnits, String status, List supplierIds, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static ArticleResponse from(Article article) { return new ArticleResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/CustomerResponse.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/CustomerResponse.java index f8ea0ed..a7a69cb 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/CustomerResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/CustomerResponse.java @@ -4,7 +4,7 @@ import de.effigenix.domain.masterdata.Customer; import de.effigenix.domain.masterdata.CustomerPreference; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; @Schema(requiredProperties = {"id", "name", "type", "billingAddress", "contactInfo", "deliveryAddresses", "preferences", "status", "createdAt", "updatedAt"}) @@ -19,8 +19,8 @@ public record CustomerResponse( @Schema(nullable = true) FrameContractResponse frameContract, List preferences, String status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static CustomerResponse from(Customer customer) { return new CustomerResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/SupplierResponse.java b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/SupplierResponse.java index c4cef8c..a05665d 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/SupplierResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/masterdata/web/dto/SupplierResponse.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.masterdata.web.dto; import de.effigenix.domain.masterdata.Supplier; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; @Schema(requiredProperties = {"id", "name", "contactInfo", "certificates", "status", "createdAt", "updatedAt"}) @@ -16,8 +16,8 @@ public record SupplierResponse( List certificates, @Schema(nullable = true) SupplierRatingResponse rating, String status, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static SupplierResponse from(Supplier supplier) { return new SupplierResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/JpaBatchNumberGenerator.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/JpaBatchNumberGenerator.java index 8bd158d..e4c0ef5 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/JpaBatchNumberGenerator.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/JpaBatchNumberGenerator.java @@ -3,10 +3,12 @@ package de.effigenix.infrastructure.production.persistence; import de.effigenix.domain.production.BatchError; import de.effigenix.domain.production.BatchNumber; import de.effigenix.domain.production.BatchNumberGenerator; -import de.effigenix.infrastructure.production.persistence.repository.BatchJpaRepository; +import de.effigenix.infrastructure.production.persistence.entity.BatchNumberSequenceEntity; +import de.effigenix.infrastructure.production.persistence.repository.BatchNumberSequenceJpaRepository; import de.effigenix.shared.common.Result; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -14,17 +16,25 @@ import java.time.LocalDate; @Profile("!no-db") public class JpaBatchNumberGenerator implements BatchNumberGenerator { - private final BatchJpaRepository batchJpaRepository; + private final BatchNumberSequenceJpaRepository sequenceRepository; - public JpaBatchNumberGenerator(BatchJpaRepository batchJpaRepository) { - this.batchJpaRepository = batchJpaRepository; + public JpaBatchNumberGenerator(BatchNumberSequenceJpaRepository sequenceRepository) { + this.sequenceRepository = sequenceRepository; } @Override + @Transactional public Result generateNext(LocalDate date) { try { - int count = batchJpaRepository.countByProductionDate(date); - int nextSequence = count + 1; + var sequence = sequenceRepository.findByProductionDate(date); + int nextSequence; + if (sequence.isPresent()) { + nextSequence = sequence.get().getLastSequence() + 1; + sequence.get().setLastSequence(nextSequence); + } else { + nextSequence = 1; + sequenceRepository.save(new BatchNumberSequenceEntity(date, 1)); + } if (nextSequence > 999) { return Result.failure(new BatchError.ValidationFailure( "Maximum batch number sequence (999) reached for date " + date)); diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchEntity.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchEntity.java index ea6a502..5d798b6 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchEntity.java @@ -4,7 +4,7 @@ import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; @Entity @Table(name = "batches") @@ -36,10 +36,10 @@ public class BatchEntity { private LocalDate bestBeforeDate; @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; + private OffsetDateTime updatedAt; protected BatchEntity() {} @@ -52,8 +52,8 @@ public class BatchEntity { String plannedQuantityUnit, LocalDate productionDate, LocalDate bestBeforeDate, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { this.id = id; this.batchNumber = batchNumber; @@ -75,6 +75,6 @@ public class BatchEntity { public String getPlannedQuantityUnit() { return plannedQuantityUnit; } public LocalDate getProductionDate() { return productionDate; } public LocalDate getBestBeforeDate() { return bestBeforeDate; } - public LocalDateTime getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } + public OffsetDateTime getCreatedAt() { return createdAt; } + public OffsetDateTime getUpdatedAt() { return updatedAt; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchNumberSequenceEntity.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchNumberSequenceEntity.java new file mode 100644 index 0000000..7482566 --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/BatchNumberSequenceEntity.java @@ -0,0 +1,37 @@ +package de.effigenix.infrastructure.production.persistence.entity; + +import jakarta.persistence.*; + +import java.time.LocalDate; + +@Entity +@Table(name = "batch_number_sequences") +public class BatchNumberSequenceEntity { + + @Id + @Column(name = "production_date", nullable = false) + private LocalDate productionDate; + + @Column(name = "last_sequence", nullable = false) + private int lastSequence; + + protected BatchNumberSequenceEntity() { + } + + public BatchNumberSequenceEntity(LocalDate productionDate, int lastSequence) { + this.productionDate = productionDate; + this.lastSequence = lastSequence; + } + + public LocalDate getProductionDate() { + return productionDate; + } + + public int getLastSequence() { + return lastSequence; + } + + public void setLastSequence(int lastSequence) { + this.lastSequence = lastSequence; + } +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/RecipeEntity.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/RecipeEntity.java index 35dd0b9..4498790 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/RecipeEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/entity/RecipeEntity.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.production.persistence.entity; import jakarta.persistence.*; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; @@ -44,10 +44,10 @@ public class RecipeEntity { private String status; @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; + private OffsetDateTime updatedAt; @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @OrderBy("position ASC") @@ -61,7 +61,7 @@ public class RecipeEntity { public RecipeEntity(String id, String name, int version, String type, String description, int yieldPercentage, Integer shelfLifeDays, BigDecimal outputQuantity, - String outputUom, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + String outputUom, String status, OffsetDateTime createdAt, OffsetDateTime updatedAt) { this.id = id; this.name = name; this.version = version; @@ -86,8 +86,8 @@ public class RecipeEntity { public BigDecimal getOutputQuantity() { return outputQuantity; } public String getOutputUom() { return outputUom; } public String getStatus() { return status; } - public LocalDateTime getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { return updatedAt; } + public OffsetDateTime getCreatedAt() { return createdAt; } + public OffsetDateTime getUpdatedAt() { return updatedAt; } public List getIngredients() { return ingredients; } public List getProductionSteps() { return productionSteps; } @@ -101,8 +101,8 @@ public class RecipeEntity { public void setOutputQuantity(BigDecimal outputQuantity) { this.outputQuantity = outputQuantity; } public void setOutputUom(String outputUom) { this.outputUom = outputUom; } public void setStatus(String status) { this.status = status; } - public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } + public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; } + public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; } public void setIngredients(List ingredients) { this.ingredients = ingredients; } public void setProductionSteps(List productionSteps) { this.productionSteps = productionSteps; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/BatchMapper.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/BatchMapper.java index cf3c33a..eee568f 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/BatchMapper.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/BatchMapper.java @@ -32,8 +32,7 @@ public class BatchMapper { BatchStatus.valueOf(entity.getStatus()), Quantity.reconstitute( entity.getPlannedQuantityAmount(), - UnitOfMeasure.valueOf(entity.getPlannedQuantityUnit()), - null, null + UnitOfMeasure.valueOf(entity.getPlannedQuantityUnit()) ), entity.getProductionDate(), entity.getBestBeforeDate(), diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/RecipeMapper.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/RecipeMapper.java index 99ebdde..9c149cd 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/RecipeMapper.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/mapper/RecipeMapper.java @@ -62,8 +62,7 @@ public class RecipeMapper { entity.getShelfLifeDays(), Quantity.reconstitute( entity.getOutputQuantity(), - UnitOfMeasure.valueOf(entity.getOutputUom()), - null, null + UnitOfMeasure.valueOf(entity.getOutputUom()) ), RecipeStatus.valueOf(entity.getStatus()), ingredients, @@ -114,8 +113,7 @@ public class RecipeMapper { entity.getArticleId(), Quantity.reconstitute( entity.getQuantity(), - UnitOfMeasure.valueOf(entity.getUom()), - null, null + UnitOfMeasure.valueOf(entity.getUom()) ), entity.getSubRecipeId(), entity.isSubstitutable() diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchJpaRepository.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchJpaRepository.java index 622114b..24bd249 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchJpaRepository.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchJpaRepository.java @@ -2,12 +2,6 @@ package de.effigenix.infrastructure.production.persistence.repository; import de.effigenix.infrastructure.production.persistence.entity.BatchEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import java.time.LocalDate; public interface BatchJpaRepository extends JpaRepository { - - @Query("SELECT COUNT(b) FROM BatchEntity b WHERE b.productionDate = :date") - int countByProductionDate(LocalDate date); } diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchNumberSequenceJpaRepository.java b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchNumberSequenceJpaRepository.java new file mode 100644 index 0000000..105608c --- /dev/null +++ b/backend/src/main/java/de/effigenix/infrastructure/production/persistence/repository/BatchNumberSequenceJpaRepository.java @@ -0,0 +1,15 @@ +package de.effigenix.infrastructure.production.persistence.repository; + +import de.effigenix.infrastructure.production.persistence.entity.BatchNumberSequenceEntity; +import jakarta.persistence.LockModeType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; + +import java.time.LocalDate; +import java.util.Optional; + +public interface BatchNumberSequenceJpaRepository extends JpaRepository { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + Optional findByProductionDate(LocalDate productionDate); +} diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/BatchResponse.java b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/BatchResponse.java index 81fefd7..1fd94c0 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/BatchResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/BatchResponse.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.production.web.dto; import de.effigenix.domain.production.Batch; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; public record BatchResponse( String id, @@ -14,8 +14,8 @@ public record BatchResponse( String plannedQuantityUnit, LocalDate productionDate, LocalDate bestBeforeDate, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static BatchResponse from(Batch batch) { return new BatchResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeResponse.java b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeResponse.java index 12a9e4c..b5a0398 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeResponse.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.production.web.dto; import de.effigenix.domain.production.Recipe; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; @Schema(requiredProperties = {"id", "name", "version", "type", "description", "yieldPercentage", "outputQuantity", "outputUom", "status", "ingredients", "productionSteps", "createdAt", "updatedAt"}) @@ -20,8 +20,8 @@ public record RecipeResponse( String status, List ingredients, List productionSteps, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static RecipeResponse from(Recipe recipe) { return new RecipeResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeSummaryResponse.java b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeSummaryResponse.java index 49171d3..380a464 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeSummaryResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/production/web/dto/RecipeSummaryResponse.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.production.web.dto; import de.effigenix.domain.production.Recipe; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; @Schema(requiredProperties = {"id", "name", "version", "type", "description", "yieldPercentage", "outputQuantity", "outputUom", "status", "ingredientCount", "stepCount", "createdAt", "updatedAt"}) public record RecipeSummaryResponse( @@ -19,8 +19,8 @@ public record RecipeSummaryResponse( String status, int ingredientCount, int stepCount, - LocalDateTime createdAt, - LocalDateTime updatedAt + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { public static RecipeSummaryResponse from(Recipe recipe) { return new RecipeSummaryResponse( diff --git a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/persistence/entity/UserEntity.java b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/persistence/entity/UserEntity.java index 3c5f92e..2877817 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/persistence/entity/UserEntity.java +++ b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/persistence/entity/UserEntity.java @@ -5,7 +5,7 @@ import jakarta.persistence.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.HashSet; import java.util.Set; @@ -48,10 +48,10 @@ public class UserEntity { @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @Column(name = "last_login") - private LocalDateTime lastLogin; + private OffsetDateTime lastLogin; // JPA requires no-arg constructor protected UserEntity() { @@ -65,8 +65,8 @@ public class UserEntity { Set roles, String branchId, UserStatus status, - LocalDateTime createdAt, - LocalDateTime lastLogin + OffsetDateTime createdAt, + OffsetDateTime lastLogin ) { this.id = id; this.username = username; @@ -136,19 +136,19 @@ public class UserEntity { this.status = status; } - public LocalDateTime getCreatedAt() { + public OffsetDateTime getCreatedAt() { return createdAt; } - public void setCreatedAt(LocalDateTime createdAt) { + public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; } - public LocalDateTime getLastLogin() { + public OffsetDateTime getLastLogin() { return lastLogin; } - public void setLastLogin(LocalDateTime lastLogin) { + public void setLastLogin(OffsetDateTime lastLogin) { this.lastLogin = lastLogin; } } diff --git a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/ErrorResponse.java b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/ErrorResponse.java index 797874f..4ebd394 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/ErrorResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/ErrorResponse.java @@ -2,7 +2,8 @@ package de.effigenix.infrastructure.usermanagement.web.dto; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; /** @@ -23,7 +24,7 @@ public record ErrorResponse( int status, @Schema(description = "Timestamp when error occurred") - LocalDateTime timestamp, + OffsetDateTime timestamp, @Schema(description = "Request path where error occurred", example = "/api/users/user-123") String path, @@ -44,7 +45,7 @@ public record ErrorResponse( code, message, status, - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), path, null ); @@ -63,7 +64,7 @@ public record ErrorResponse( "VALIDATION_ERROR", message, status, - LocalDateTime.now(), + OffsetDateTime.now(ZoneOffset.UTC), path, validationErrors ); diff --git a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/LoginResponse.java b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/LoginResponse.java index 9294871..b106607 100644 --- a/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/LoginResponse.java +++ b/backend/src/main/java/de/effigenix/infrastructure/usermanagement/web/dto/LoginResponse.java @@ -3,7 +3,7 @@ package de.effigenix.infrastructure.usermanagement.web.dto; import de.effigenix.application.usermanagement.dto.SessionToken; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; /** * Response DTO for successful login. @@ -25,7 +25,7 @@ public record LoginResponse( long expiresIn, @Schema(description = "Token expiration timestamp") - LocalDateTime expiresAt, + OffsetDateTime expiresAt, @Schema(description = "Refresh token for obtaining new access token") String refreshToken diff --git a/backend/src/main/java/de/effigenix/shared/common/Quantity.java b/backend/src/main/java/de/effigenix/shared/common/Quantity.java index cf78162..248f4e4 100644 --- a/backend/src/main/java/de/effigenix/shared/common/Quantity.java +++ b/backend/src/main/java/de/effigenix/shared/common/Quantity.java @@ -69,7 +69,14 @@ public final class Quantity { } /** - * Reconstitutes a Quantity from persistence. No validation. + * Reconstitutes a simple Quantity from persistence. No validation. + */ + public static Quantity reconstitute(BigDecimal amount, UnitOfMeasure uom) { + return new Quantity(amount, uom, null, null); + } + + /** + * Reconstitutes a dual Quantity (catch-weight) from persistence. No validation. */ public static Quantity reconstitute(BigDecimal amount, UnitOfMeasure uom, BigDecimal secondaryAmount, UnitOfMeasure secondaryUom) { diff --git a/backend/src/main/resources/db/changelog/changes/016-create-batch-number-sequences-table.xml b/backend/src/main/resources/db/changelog/changes/016-create-batch-number-sequences-table.xml new file mode 100644 index 0000000..6d8698d --- /dev/null +++ b/backend/src/main/resources/db/changelog/changes/016-create-batch-number-sequences-table.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/db/changelog/changes/017-timestamps-to-timestamptz.xml b/backend/src/main/resources/db/changelog/changes/017-timestamps-to-timestamptz.xml new file mode 100644 index 0000000..c24d73f --- /dev/null +++ b/backend/src/main/resources/db/changelog/changes/017-timestamps-to-timestamptz.xml @@ -0,0 +1,36 @@ + + + + + Migrate all TIMESTAMP columns to TIMESTAMP WITH TIME ZONE for consistent timezone handling + + + ALTER TABLE users ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE users ALTER COLUMN last_login TYPE TIMESTAMP WITH TIME ZONE; + + + ALTER TABLE audit_logs ALTER COLUMN timestamp TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE audit_logs ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + + + ALTER TABLE articles ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE articles ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; + + + ALTER TABLE suppliers ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE suppliers ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; + + + ALTER TABLE customers ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE customers ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; + + + ALTER TABLE recipes ALTER COLUMN created_at TYPE TIMESTAMP WITH TIME ZONE; + ALTER TABLE recipes ALTER COLUMN updated_at TYPE TIMESTAMP WITH TIME ZONE; + + + diff --git a/backend/src/main/resources/db/changelog/db.changelog-master.xml b/backend/src/main/resources/db/changelog/db.changelog-master.xml index 75cea8c..71b7285 100644 --- a/backend/src/main/resources/db/changelog/db.changelog-master.xml +++ b/backend/src/main/resources/db/changelog/db.changelog-master.xml @@ -20,5 +20,7 @@ + + diff --git a/backend/src/test/java/de/effigenix/application/inventory/AddStockBatchTest.java b/backend/src/test/java/de/effigenix/application/inventory/AddStockBatchTest.java index 581090f..d5bc719 100644 --- a/backend/src/test/java/de/effigenix/application/inventory/AddStockBatchTest.java +++ b/backend/src/test/java/de/effigenix/application/inventory/AddStockBatchTest.java @@ -119,7 +119,7 @@ class AddStockBatchTest { List.of(StockBatch.reconstitute( StockBatchId.generate(), new BatchReference("BATCH-001", BatchType.PRODUCED), - Quantity.reconstitute(new BigDecimal("5"), UnitOfMeasure.KILOGRAM, null, null), + Quantity.reconstitute(new BigDecimal("5"), UnitOfMeasure.KILOGRAM), LocalDate.of(2026, 12, 31), StockBatchStatus.AVAILABLE, Instant.now() diff --git a/backend/src/test/java/de/effigenix/application/inventory/RemoveStockBatchTest.java b/backend/src/test/java/de/effigenix/application/inventory/RemoveStockBatchTest.java index 752960a..c87d3e1 100644 --- a/backend/src/test/java/de/effigenix/application/inventory/RemoveStockBatchTest.java +++ b/backend/src/test/java/de/effigenix/application/inventory/RemoveStockBatchTest.java @@ -43,7 +43,7 @@ class RemoveStockBatchTest { var batch = StockBatch.reconstitute( batchId, new BatchReference("BATCH-001", BatchType.PRODUCED), - Quantity.reconstitute(new BigDecimal("10"), UnitOfMeasure.KILOGRAM, null, null), + Quantity.reconstitute(new BigDecimal("10"), UnitOfMeasure.KILOGRAM), LocalDate.of(2026, 12, 31), StockBatchStatus.AVAILABLE, Instant.now() diff --git a/backend/src/test/java/de/effigenix/application/production/ActivateRecipeTest.java b/backend/src/test/java/de/effigenix/application/production/ActivateRecipeTest.java index 6d67b00..678105b 100644 --- a/backend/src/test/java/de/effigenix/application/production/ActivateRecipeTest.java +++ b/backend/src/test/java/de/effigenix/application/production/ActivateRecipeTest.java @@ -15,7 +15,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; @@ -61,7 +62,7 @@ class ActivateRecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } diff --git a/backend/src/test/java/de/effigenix/application/production/ArchiveRecipeTest.java b/backend/src/test/java/de/effigenix/application/production/ArchiveRecipeTest.java index adfe970..eca7cf0 100644 --- a/backend/src/test/java/de/effigenix/application/production/ArchiveRecipeTest.java +++ b/backend/src/test/java/de/effigenix/application/production/ArchiveRecipeTest.java @@ -15,7 +15,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; @@ -45,7 +46,7 @@ class ArchiveRecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } @@ -118,7 +119,7 @@ class ArchiveRecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ARCHIVED, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); when(authPort.can(performedBy, ProductionAction.RECIPE_WRITE)).thenReturn(true); when(recipeRepository.findById(RecipeId.of("recipe-2"))).thenReturn(Result.success(Optional.of(recipe))); diff --git a/backend/src/test/java/de/effigenix/application/production/GetRecipeTest.java b/backend/src/test/java/de/effigenix/application/production/GetRecipeTest.java index 3804178..cb2348e 100644 --- a/backend/src/test/java/de/effigenix/application/production/GetRecipeTest.java +++ b/backend/src/test/java/de/effigenix/application/production/GetRecipeTest.java @@ -14,7 +14,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; @@ -43,7 +44,7 @@ class GetRecipeTest { "Beschreibung", new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } diff --git a/backend/src/test/java/de/effigenix/application/production/ListRecipesTest.java b/backend/src/test/java/de/effigenix/application/production/ListRecipesTest.java index f46df29..86ffd38 100644 --- a/backend/src/test/java/de/effigenix/application/production/ListRecipesTest.java +++ b/backend/src/test/java/de/effigenix/application/production/ListRecipesTest.java @@ -14,7 +14,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +43,7 @@ class ListRecipesTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), status, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } diff --git a/backend/src/test/java/de/effigenix/application/production/PlanBatchTest.java b/backend/src/test/java/de/effigenix/application/production/PlanBatchTest.java index 4685a51..7190a1c 100644 --- a/backend/src/test/java/de/effigenix/application/production/PlanBatchTest.java +++ b/backend/src/test/java/de/effigenix/application/production/PlanBatchTest.java @@ -17,7 +17,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; @@ -57,7 +58,7 @@ class PlanBatchTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } @@ -67,7 +68,7 @@ class PlanBatchTest { null, new YieldPercentage(85), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.DRAFT, List.of(), List.of(), - LocalDateTime.now(), LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC) ); } diff --git a/backend/src/test/java/de/effigenix/application/production/RecipeCycleCheckerTest.java b/backend/src/test/java/de/effigenix/application/production/RecipeCycleCheckerTest.java index 7bee407..a79b485 100644 --- a/backend/src/test/java/de/effigenix/application/production/RecipeCycleCheckerTest.java +++ b/backend/src/test/java/de/effigenix/application/production/RecipeCycleCheckerTest.java @@ -13,7 +13,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; @@ -48,7 +49,7 @@ class RecipeCycleCheckerTest { null, new YieldPercentage(100), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.DRAFT, ingredients, List.of(), - LocalDateTime.now(), LocalDateTime.now()); + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC)); } private Recipe recipeWithoutSubRecipes(String id) { @@ -145,7 +146,7 @@ class RecipeCycleCheckerTest { null, new YieldPercentage(100), 14, Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.DRAFT, ingredients, List.of(), - LocalDateTime.now(), LocalDateTime.now()); + OffsetDateTime.now(ZoneOffset.UTC), OffsetDateTime.now(ZoneOffset.UTC)); when(recipeRepository.findById(RecipeId.of("B"))).thenReturn(Result.success(Optional.of(recipeB))); when(recipeRepository.findById(RecipeId.of("C"))).thenReturn(Result.success(Optional.of(recipeWithoutSubRecipes("C")))); diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/AssignRoleTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/AssignRoleTest.java index 1249493..5d0bd6f 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/AssignRoleTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/AssignRoleTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -42,7 +43,7 @@ class AssignRoleTest { testUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Production Worker"); } diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/AuthenticateUserTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/AuthenticateUserTest.java index 6effafc..b767e9f 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/AuthenticateUserTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/AuthenticateUserTest.java @@ -13,7 +13,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -45,10 +46,10 @@ class AuthenticateUserTest { testUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", validPasswordHash, - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); - sessionToken = new SessionToken("jwt-token", "Bearer", 3600L, LocalDateTime.now().plusSeconds(3600), "refresh-token"); + sessionToken = new SessionToken("jwt-token", "Bearer", 3600L, OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(3600), "refresh-token"); } @Test @@ -84,7 +85,7 @@ class AuthenticateUserTest { void should_FailWithLockedUser_When_UserStatusIsLocked() { User lockedUser = User.reconstitute( UserId.of("user-2"), "john.doe", "john@example.com", validPasswordHash, - new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.LOCKED, OffsetDateTime.now(ZoneOffset.UTC), null ); when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(lockedUser))); @@ -100,7 +101,7 @@ class AuthenticateUserTest { void should_FailWithInactiveUser_When_UserStatusIsInactive() { User inactiveUser = User.reconstitute( UserId.of("user-3"), "john.doe", "john@example.com", validPasswordHash, - new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.INACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(inactiveUser))); @@ -143,7 +144,7 @@ class AuthenticateUserTest { void should_NotCreateSession_When_UserLocked() { User lockedUser = User.reconstitute( UserId.of("user-4"), "john.doe", "john@example.com", validPasswordHash, - new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.LOCKED, OffsetDateTime.now(ZoneOffset.UTC), null ); when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(lockedUser))); diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/ChangePasswordTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/ChangePasswordTest.java index ed5144f..2b8be45 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/ChangePasswordTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/ChangePasswordTest.java @@ -12,7 +12,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -44,7 +45,7 @@ class ChangePasswordTest { testUser = User.reconstitute( UserId.of("user-123"), "john.doe", "john@example.com", oldPasswordHash, - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); validCommand = new ChangePasswordCommand("user-123", "OldPassword123!", "NewPassword456!"); diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/GetUserTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/GetUserTest.java index 7a9e769..f720474 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/GetUserTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/GetUserTest.java @@ -12,7 +12,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -37,7 +38,7 @@ class GetUserTest { testUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); } diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/ListUsersTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/ListUsersTest.java index 86352e1..5ed91a3 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/ListUsersTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/ListUsersTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.List; @@ -39,12 +40,12 @@ class ListUsersTest { user1 = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); user2 = User.reconstitute( UserId.of("user-2"), "jane.doe", "jane@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); } diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/LockUserTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/LockUserTest.java index a1b1c14..007cefc 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/LockUserTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/LockUserTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -40,7 +41,7 @@ class LockUserTest { activeUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); } @@ -100,7 +101,7 @@ class LockUserTest { User lockedUser = User.reconstitute( UserId.of("user-2"), "jane.doe", "jane@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.LOCKED, OffsetDateTime.now(ZoneOffset.UTC), null ); when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true); when(userRepository.findById(UserId.of("user-2"))).thenReturn(Result.success(Optional.of(lockedUser))); @@ -118,7 +119,7 @@ class LockUserTest { User inactiveUser = User.reconstitute( UserId.of("user-3"), "bob", "bob@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.INACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true); when(userRepository.findById(UserId.of("user-3"))).thenReturn(Result.success(Optional.of(inactiveUser))); diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/RemoveRoleTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/RemoveRoleTest.java index 67d8c74..a1cfde4 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/RemoveRoleTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/RemoveRoleTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -44,7 +45,7 @@ class RemoveRoleTest { userWithRole = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(Set.of(workerRole)), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(Set.of(workerRole)), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); } diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/UnlockUserTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/UnlockUserTest.java index aabc231..169f479 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/UnlockUserTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/UnlockUserTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -40,7 +41,7 @@ class UnlockUserTest { lockedUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.LOCKED, OffsetDateTime.now(ZoneOffset.UTC), null ); } @@ -100,7 +101,7 @@ class UnlockUserTest { User activeUser = User.reconstitute( UserId.of("user-2"), "jane.doe", "jane@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true); when(userRepository.findById(UserId.of("user-2"))).thenReturn(Result.success(Optional.of(activeUser))); @@ -118,7 +119,7 @@ class UnlockUserTest { User inactiveUser = User.reconstitute( UserId.of("user-3"), "bob", "bob@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.INACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true); when(userRepository.findById(UserId.of("user-3"))).thenReturn(Result.success(Optional.of(inactiveUser))); diff --git a/backend/src/test/java/de/effigenix/application/usermanagement/UpdateUserTest.java b/backend/src/test/java/de/effigenix/application/usermanagement/UpdateUserTest.java index e3151ce..c985760 100644 --- a/backend/src/test/java/de/effigenix/application/usermanagement/UpdateUserTest.java +++ b/backend/src/test/java/de/effigenix/application/usermanagement/UpdateUserTest.java @@ -13,7 +13,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Optional; @@ -40,7 +41,7 @@ class UpdateUserTest { testUser = User.reconstitute( UserId.of("user-1"), "john.doe", "john@example.com", new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"), - new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null + new HashSet<>(), "branch-1", UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null ); } diff --git a/backend/src/test/java/de/effigenix/domain/inventory/StockTest.java b/backend/src/test/java/de/effigenix/domain/inventory/StockTest.java index 8a7c474..5067a4b 100644 --- a/backend/src/test/java/de/effigenix/domain/inventory/StockTest.java +++ b/backend/src/test/java/de/effigenix/domain/inventory/StockTest.java @@ -266,7 +266,7 @@ class StockTest { var id = StockId.generate(); var articleId = ArticleId.of("article-1"); var locationId = StorageLocationId.of("location-1"); - var quantity = Quantity.reconstitute(new BigDecimal("10"), UnitOfMeasure.KILOGRAM, null, null); + var quantity = Quantity.reconstitute(new BigDecimal("10"), UnitOfMeasure.KILOGRAM); var minimumLevel = new MinimumLevel(quantity); var minimumShelfLife = new MinimumShelfLife(30); @@ -606,7 +606,7 @@ class StockTest { var batch = StockBatch.reconstitute( StockBatchId.generate(), new BatchReference("BATCH-001", BatchType.PRODUCED), - Quantity.reconstitute(new BigDecimal(amount), uom, null, null), + Quantity.reconstitute(new BigDecimal(amount), uom), LocalDate.of(2026, 12, 31), status, Instant.now() diff --git a/backend/src/test/java/de/effigenix/domain/production/BatchTest.java b/backend/src/test/java/de/effigenix/domain/production/BatchTest.java index 38d1e68..d4e3a20 100644 --- a/backend/src/test/java/de/effigenix/domain/production/BatchTest.java +++ b/backend/src/test/java/de/effigenix/domain/production/BatchTest.java @@ -8,7 +8,8 @@ import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import static org.assertj.core.api.Assertions.assertThat; @@ -187,8 +188,8 @@ class BatchTest { Quantity.of(new BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), PRODUCTION_DATE, BEST_BEFORE_DATE, - LocalDateTime.now(), - LocalDateTime.now() + OffsetDateTime.now(ZoneOffset.UTC), + OffsetDateTime.now(ZoneOffset.UTC) ); assertThat(batch.id().value()).isEqualTo("batch-1"); diff --git a/backend/src/test/java/de/effigenix/domain/production/RecipeTest.java b/backend/src/test/java/de/effigenix/domain/production/RecipeTest.java index ee484d0..ca416c9 100644 --- a/backend/src/test/java/de/effigenix/domain/production/RecipeTest.java +++ b/backend/src/test/java/de/effigenix/domain/production/RecipeTest.java @@ -239,7 +239,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.addIngredient(validIngredientDraft(1)); @@ -322,7 +322,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.removeIngredient(IngredientId.generate()); @@ -376,7 +376,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.addProductionStep(new ProductionStepDraft(1, "Mischen", null, null)); @@ -445,7 +445,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.removeProductionStep(1); @@ -507,7 +507,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.activate(); @@ -528,7 +528,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ARCHIVED, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.activate(); @@ -554,7 +554,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ACTIVE, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var updatedBefore = recipe.updatedAt(); @@ -588,7 +588,7 @@ class RecipeTest { null, new YieldPercentage(85), 14, Quantity.of(new java.math.BigDecimal("100"), UnitOfMeasure.KILOGRAM).unsafeGetValue(), RecipeStatus.ARCHIVED, List.of(), List.of(), - java.time.LocalDateTime.now(), java.time.LocalDateTime.now() + java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC), java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC) ); var result = recipe.archive(); diff --git a/backend/src/test/java/de/effigenix/domain/usermanagement/UserTest.java b/backend/src/test/java/de/effigenix/domain/usermanagement/UserTest.java index b6fc046..c7687c6 100644 --- a/backend/src/test/java/de/effigenix/domain/usermanagement/UserTest.java +++ b/backend/src/test/java/de/effigenix/domain/usermanagement/UserTest.java @@ -7,7 +7,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.*; import static org.assertj.core.api.Assertions.*; @@ -25,7 +26,7 @@ class UserTest { private PasswordHash passwordHash; private Set roles; private String branchId; - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @BeforeEach void setUp() { @@ -35,7 +36,7 @@ class UserTest { passwordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"); roles = new HashSet<>(); branchId = "branch-1"; - createdAt = LocalDateTime.now(); + createdAt = OffsetDateTime.now(ZoneOffset.UTC); } @Test @@ -56,9 +57,9 @@ class UserTest { @Test @DisplayName("should_SetDefaultCreatedAtToNow_When_NullProvidedForCreatedAt") void should_SetDefaultCreatedAtToNow_When_NullProvidedForCreatedAt() { - LocalDateTime before = LocalDateTime.now(); + OffsetDateTime before = OffsetDateTime.now(ZoneOffset.UTC); User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, null, null); - LocalDateTime after = LocalDateTime.now(); + OffsetDateTime after = OffsetDateTime.now(ZoneOffset.UTC); assertThat(user.createdAt()).isNotNull(); assertThat(user.createdAt()).isBetween(before, after); @@ -129,7 +130,7 @@ class UserTest { @DisplayName("should_ReturnNewUserWithLastLogin_When_WithLastLoginCalled") void should_ReturnNewUserWithLastLogin_When_WithLastLoginCalled() { User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue(); - LocalDateTime now = LocalDateTime.now(); + OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); User updated = user.withLastLogin(now).unsafeGetValue(); diff --git a/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java index d776675..bd3261a 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/AbstractIntegrationTest.java @@ -18,7 +18,8 @@ import org.springframework.transaction.annotation.Transactional; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.Date; import java.util.Set; import java.util.UUID; @@ -102,7 +103,7 @@ public abstract class AbstractIntegrationTest { UserEntity user = new UserEntity( UUID.randomUUID().toString(), username, email, BCRYPT_PASS123, roles, - branchId, UserStatus.ACTIVE, LocalDateTime.now(), null); + branchId, UserStatus.ACTIVE, OffsetDateTime.now(ZoneOffset.UTC), null); return userRepository.save(user); } } diff --git a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/persistence/mapper/UserMapperTest.java b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/persistence/mapper/UserMapperTest.java index 0283a48..f284837 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/persistence/mapper/UserMapperTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/persistence/mapper/UserMapperTest.java @@ -7,7 +7,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.HashSet; import java.util.Set; @@ -29,11 +30,11 @@ class UserMapperTest { private User domainUser; private UserEntity jpaEntity; - private LocalDateTime createdAt; + private OffsetDateTime createdAt; @BeforeEach void setUp() { - createdAt = LocalDateTime.now(); + createdAt = OffsetDateTime.now(ZoneOffset.UTC); // Create JPA entity first jpaEntity = new UserEntity( @@ -112,7 +113,7 @@ class UserMapperTest { @DisplayName("should_PreserveAllUserFields_When_MappingToEntity") void should_PreserveAllUserFields_When_MappingToEntity() { // Arrange - LocalDateTime lastLogin = LocalDateTime.now(); + OffsetDateTime lastLogin = OffsetDateTime.now(ZoneOffset.UTC); UserEntity sourceEntity = new UserEntity( "user-456", "jane.smith", @@ -144,7 +145,7 @@ class UserMapperTest { @DisplayName("should_PreserveAllEntityFields_When_MappingToDomain") void should_PreserveAllEntityFields_When_MappingToDomain() { // Arrange - LocalDateTime lastLogin = LocalDateTime.now(); + OffsetDateTime lastLogin = OffsetDateTime.now(ZoneOffset.UTC); UserEntity entityWithLastLogin = new UserEntity( "user-789", "bob.jones", diff --git a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java index e6753cb..abe4e2f 100644 --- a/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java +++ b/backend/src/test/java/de/effigenix/infrastructure/usermanagement/web/SecurityIntegrationTest.java @@ -18,7 +18,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; -import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Set; import java.util.UUID; @@ -327,7 +328,7 @@ class SecurityIntegrationTest extends AbstractIntegrationTest { .orElseThrow(); assertThat(auditLog.getTimestamp()).isNotNull(); - assertThat(auditLog.getTimestamp()).isAfter(LocalDateTime.now().minusMinutes(1)); + assertThat(auditLog.getTimestamp()).isAfter(OffsetDateTime.now(ZoneOffset.UTC).minusMinutes(1)); } @Test