stock_movements hat einen FK auf stock_batches mit RESTRICT, daher
schlug das bisherige Delete-All + Re-Insert Pattern fehl sobald
Bestandsbewegungen existierten (HTTP 500 bei Reservierung).
Lösung: Upsert-Pattern (UPDATE bestehende, INSERT neue, DELETE entfernte).
traceForward aus BatchTraceabilityService in BatchRepository verschoben.
Statt einer Query pro Knoten (N+1) wird jetzt eine IN(:parentIds)-Query
pro Tiefenebene ausgeführt (max. maxDepth Queries statt N).
BFS-Traversierung über Chargen-Genealogie (batch_consumptions.input_batch_id)
mit Cycle-Detection und Max-Depth-Guard. REST-Endpoint GET /{id}/trace-forward
liefert flache Liste mit Tiefenangabe für betroffene Endprodukt-Chargen.
- GlobalExceptionHandler und ErrorResponse nach infrastructure.shared
extrahieren (war fälschlich in usermanagement)
- CountItem.deviation() prüft UOM-Kompatibilität
- InvalidInventoryCountId Error-Typ für null/blank ID (400 statt 404)
- saveChildren() auf UPSERT (UPDATE→INSERT) mit Orphan-Cleanup umstellen
- logger.trace → logger.warn bei DB-Fehlern
- Stocks ohne Batches in CreateInventoryCount überspringen
- AuthorizationPort Defense in Depth in alle 3 InventoryCount Use Cases
- Kombinierter DB-Index auf (storage_location_id, status)
InventoryCount-Aggregate mit CountItem-Entities, Auto-Populate aus
Stock-Daten, vollständige DDD-Schichten inkl. Edge-Case-Tests und
Jazzer-Fuzz-Test. 1909 Tests grün.
- BatchNumber in allen ProductionOrder-Endpoints via BatchRepository auflösen
- BatchCreationFailed Error-Variante statt generischem ValidationFailure
- bestBeforeDate-Berechnung als Recipe.calculateBestBeforeDate() in die Domain verschoben
TUI-Anbindung für Reservierung bestätigen (US-4.3), Produktionsauftrag
umterminieren und filtern (US-P17). Status-Werte CREATED→PLANNED und
IN_PRODUCTION→IN_PROGRESS korrigiert. Fehlenden GET /{id} Endpoint für
Produktionsaufträge im Backend ergänzt.
Coverage-guided Fuzz-Tests für ProductionOrder, Recipe und Batch.
Jeder Test fuzzt create() + zufällige Sequenzen aller Mutations-Methoden,
um unbehandelte Exceptions bei beliebigen Inputs aufzudecken.
- jazzer-junit 0.24.0 als Test-Dependency
- Maven-Profile: -Pfuzz (echtes Fuzzing), -Pfuzz-regression (Crash-Replay)
- Surefire: FuzzTests im Default-Lauf ausgeschlossen, reuseForks=false
- Makefile: make fuzz, make fuzz/regression, make fuzz/single
- .gitignore: .cifuzz-corpus/ ausgeschlossen
- MovementType-Ableitung via ReferenceType.toMovementType() in Domain Layer
- Doppelten Batch-Lookup in Stock.confirmReservation() eliminiert
- StockError.MovementCreationFailed statt RepositoryFailure für Domain-Fehler
- 204 No Content statt 200 OK (konsistent mit releaseReservation)
- Batches mit Menge 0 nicht mehr entfernen (FK stock_movements → stock_batches)
Allokierte Mengen werden physisch aus den Chargen abgezogen,
StockMovements erzeugt und die Reservation entfernt.
MovementType wird aus ReferenceType abgeleitet
(PRODUCTION_ORDER→PRODUCTION_CONSUMPTION, SALE_ORDER→SALE).
RescheduleNotAllowed Error-Typ statt InvalidStatusTransition(status, status),
Null-Guard für newDate, Controller-Parameter als Enum statt String (verhindert
500 bei ungültigem Status), partielle Datumsangaben als 400 ablehnen,
ORDER BY in findAll() konsistent mit gefilterten Queries.
Reschedule (PLANNED/RELEASED) mit Datumsvalidierung und List-Endpoint
mit optionaler Filterung nach Datum/Status als Full Vertical Slice.
Lasttests um neue Szenarien erweitert.
cancel() nimmt jetzt einen reason-Parameter entgegen und speichert ihn
im Aggregat, wie im DDD-Modell (04-production-bc.md) spezifiziert.
Liquibase-Migration für cancelled_reason-Spalte ergänzt.
DatabaseProfileInitializer setzt Autoconfigure-Exclusions programmatisch,
da application-no-db.yml bei spätem Profil-Aktivierung nicht geladen wird.
SpringUnitOfWork mit @Profile("!no-db") ausgeschlossen. Stub-Beans für
BatchNumberGenerator, BatchRepository, ProductionOrderRepository,
StockMovementRepository und UnitOfWork ergänzt. generate-openapi.sh
nutzt korrektes -Dspring-boot.run.profiles=no-db.
PostgreSQL JDBC-Treiber kann java.time.Instant nicht direkt an
TIMESTAMP WITH TIME ZONE binden. Schreibparameter werden nun via
atOffset(UTC) konvertiert, Lesezugriffe über OffsetDateTime.toInstant().
Letzter BC migriert: JPA/Hibernate durch JdbcClient ersetzt,
@Transactional durch UnitOfWork-Pattern in allen schreibenden Use Cases.
- 3 neue Jdbc-Repos: JdbcStorageLocationRepository, JdbcStockMovementRepository,
JdbcStockRepository (4-Tabellen-Aggregat mit Batches, Reservations, Allocations)
- 20 Use Cases angepasst (UoW für schreibende, @Transactional entfernt für lesende)
- 15 alte JPA-Dateien gelöscht (6 Entities, 3 Mapper, 3 Adapter, 3 Spring Data Repos)
- 9 Unit-Tests mit UoW-Mock-Pattern aktualisiert
Changeset 031 (performed_at-Index) entfernt, da bereits in 028 enthalten.
StockMovementControllerIntegrationTest an JdbcClient-basierte AbstractIntegrationTest migriert
und fehlende Article-FK durch createArticleId() behoben.
Migriert UserManagement und MasterData BCs von JPA/Spring Data auf
JdbcClient + UnitOfWork, analog zum Production-BC. Inventory bleibt auf JPA.
- 6 neue JdbcClient-Repositories (User, Role, Article, Supplier, Customer, ProductCategory)
- 45 Use Cases: UoW für schreibende, @Transactional entfernt bei allen
- AbstractIntegrationTest + 20 Integration-Tests auf JdbcClient umgestellt
- 12 Unit-Test-Klassen mit UoW-Mock erweitert
- 34 alte JPA-Dateien gelöscht (Entities, Spring Data Repos, Adapter, Mapper)
RELEASED ProductionOrder kann mit einer PLANNED Batch verknüpft und
in Produktion gestartet werden. Dabei wechselt der Order auf IN_PROGRESS
und die Batch auf IN_PRODUCTION. Neuer REST-Endpoint POST /{id}/start,
StartOrderProduction Use Case, BatchAlreadyAssigned Error, Liquibase-
Migration für batch_id FK auf production_orders.
Erweitert die StockMovement-Abfrage um batchReference- und from/to-Filter
mit Filter-Priorität stockId > articleId > batchReference > movementType > from/to.
Inkl. DB-Index auf batch_id, Unit-/Integrationstests und Lasttest-Szenarien.
Backend: Country-Record (Shared Kernel), InMemoryCountryRepository mit
~249 Ländern und DACH-Priorisierung, ListCountries-UseCase,
GET /api/countries?q= Endpoint.
Frontend: CountryPicker-Komponente mit Fuzzy-Suche, DACH-Favoriten bei
leerem Query. SupplierCreate-, CustomerCreate- und AddDeliveryAddress-
Screens verwenden jetzt den CountryPicker statt Freitext. Detail-Screens
zeigen den Ländercode in der Adressanzeige.
Closes#71
ListStorageLocations ignorierte active=false und gab stattdessen
alle Lagerorte zurück. Jetzt wird bei gesetztem active-Parameter
korrekt nach aktivem/inaktivem Status gefiltert.
DELETE /api/inventory/stocks/{stockId}/reservations/{reservationId}
gibt eine bestehende Reservierung frei und stellt die verfügbare
Menge wieder her. Zusätzlich Liquibase-Changeset 025 idempotent
gemacht (ON CONFLICT DO NOTHING).
Implementiert Story 4.1: Reservierung von Beständen mit automatischer
FEFO-Allokation (First-Expired-First-Out) über verfügbare Chargen.
Domain: Reservation-Entity, StockBatchAllocation, ReservationDraft,
FEFO-Logik in Stock.reserve(), availableQuantity() berücksichtigt
bestehende Allokationen. Neue Error-Varianten für InsufficientStock,
InvalidReferenceType, InvalidReservationPriority, ReservationNotFound.
API: POST /api/inventory/stocks/{stockId}/reservations → 201 Created.
Liquibase: reservations + stock_batch_allocations Tabellen mit FK- und
CHECK-Constraints.
Tests: 43 neue Tests (22 Domain, 10 UseCase, 11 Integration) für
FEFO-Logik, Validierung, Mengenprüfung, Auth und Edge Cases.
ProductionOrder-Aggregate mit DDD + Clean Architecture eingeführt.
Produktionsleiter können Aufträge mit Rezept, Menge, Termin und
Priorität planen. Validierung: Quantity > 0, PlannedDate nicht in
Vergangenheit, Priority (LOW/NORMAL/HIGH/URGENT), Recipe ACTIVE.
DeactivateStorageLocation prüft jetzt via StockRepository ob Bestände am
Lagerort existieren bevor deaktiviert wird (Story 1.2 Akzeptanzkriterium).
5 Unit Tests und 1 Integrationstest für den neuen Check.
Frontend: @sentry/node mit instrument.ts, globale Error-Handler, 5xx-Interceptor.
Backend: sentry-spring-boot-starter-jakarta, Sentry.captureException im GlobalExceptionHandler.
Konfiguration über SENTRY_DSN Env-Variable, Bugsink via make bugsink startbar.
GET /api/inventory/stocks/below-minimum zeigt Bestände, deren verfügbare
Menge (AVAILABLE + EXPIRING_SOON) unter dem konfigurierten Mindestbestand
liegt. DB-Prefilter via findAllWithMinimumLevel() + Domain-Filter als
Defense in Depth. StockResponse.from() nutzt nun Stock.availableQuantity()
statt duplizierter Logik.
Closes#11
PLANNED und IN_PRODUCTION Chargen können mit Angabe eines
Stornierungsgrundes storniert werden. COMPLETED und bereits
CANCELLED Chargen werden abgelehnt.
Batch.complete() mit Ist-Menge, Ausschuss und Bemerkungen.
Invarianten: nur IN_PRODUCTION→COMPLETED, mind. eine Consumption,
ActualQuantity > 0, Waste >= 0. Full Vertical Slice mit Domain Event
Stub (BatchCompleted), REST POST /api/batches/{id}/complete und
Liquibase-Migration für die neuen Spalten.
Täglicher Scheduler prüft alle StockBatches und setzt Status auf
EXPIRED bzw. EXPIRING_SOON basierend auf MHD und MinimumShelfLife.
Reihenfolge: erst EXPIRED, dann EXPIRING_SOON — BLOCKED bleibt unangetastet.
- Managed-Entity-Update statt detach/merge (verhindert DELETE+INSERT-Churn)
- @Version für Optimistic Locking mit ConcurrentModification-Error
- Null-Checks für quantityUsed/quantityUnit vor BigDecimal-Parsing
- Duplicate-Check nach Consumption.create() für robustere Validierung
- FetchType.EAGER→LAZY für BatchEntity.consumptions
- Liquibase-Migration 020 für version-Spalte
PLANNED-Chargen können in Produktion genommen werden (IN_PRODUCTION),
anschließend wird der Rohstoff-Verbrauch pro InputBatch dokumentiert.
Bildet die Grundlage für die Chargen-Genealogie (Tracing).
Stock.update(StockUpdateDraft) ermöglicht optionale Aktualisierung von
MinimumLevel und MinimumShelfLife mit identischer Validierung wie create().
PUT /api/inventory/stocks/{id} Endpoint, UpdateStock Use Case + Tests.
Closes#9
Full Vertical Slice für Batch-Lese-Endpoints:
- GET /api/production/batches/{id}
- GET /api/production/batches (Filter: status, productionDate, articleId)
- GET /api/production/batches/by-number/{batchNumber}
articleId-Filter löst über RecipeRepository.findByArticleId() die
zugehörigen Recipes auf und sucht dann Batches per findByRecipeIds().
Closes#34
- StockError.InvalidFilterCombination statt InvalidArticleId für
ungültige Filter-Kombination in ListStocks
- CreateStockResponse als eigenständiges DTO für POST createStock
(ohne batches/quantities), StockResponse bleibt für GET-Endpoints
- MinimumLevelResponse als shared Top-Level-Record extrahiert
Query-Endpoints für Stock-Aggregate: GET /api/inventory/stocks (mit
optionalen Filtern storageLocationId/articleId) und GET /api/inventory/stocks/{id}.
StockResponse enthält nun Batches, totalQuantity, availableQuantity und
quantityUnit. Abgesichert über @PreAuthorize STOCK_READ.