mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 12:09:35 +01:00
- Move Java backend to backend/ directory - Create frontend/ directory for TypeScript TUI and future WebUI - Update .gitignore for Node.js and worktrees - Update README.md with new repository structure - Copy documentation to backend/
163 lines
5.3 KiB
Markdown
163 lines
5.3 KiB
Markdown
# Bestandsführungs-Kontext - Detailliertes Domain Model
|
|
|
|
**Bounded Context:** Bestandsführung
|
|
**Domain-Typ:** KERN
|
|
**Verantwortung:** Chargen-basierte Bestandsführung, Rückverfolgbarkeit, MHD-Tracking
|
|
|
|
---
|
|
|
|
## Aggregate
|
|
|
|
### 1. Bestand (Aggregate Root)
|
|
|
|
**Struktur:**
|
|
```
|
|
Bestand (Aggregate Root)
|
|
├── BestandId (Wertobjekt)
|
|
├── ArtikelId (Wertobjekt) - Referenz zu Stammdaten
|
|
├── LagerortId (Wertobjekt)
|
|
├── FilialId (Wertobjekt)
|
|
├── Bestandsmenge (Wertobjekt: Menge) - Aktueller Gesamtbestand
|
|
├── Chargen[] (Entität) - Chargen-spezifischer Bestand (KRITISCH!)
|
|
│ ├── ChargenId (Wertobjekt) - ProduktionsChargenId ODER LieferantenChargenId
|
|
│ ├── ChargenTyp (Wertobjekt: PRODUZIERT | EINGEKAUFT)
|
|
│ ├── Menge (Wertobjekt)
|
|
│ ├── Verfallsdatum (Wertobjekt: MHD)
|
|
│ ├── EingegangenenAm (Wertobjekt)
|
|
│ └── Status (Wertobjekt: VERFUEGBAR | RESERVIERT | ABGELAUFEN | VERKAUFT)
|
|
├── MinimalBestandsmenge (Wertobjekt) - Für Warnungen bei niedrigem Bestand
|
|
└── Nachbestellpunkt (Wertobjekt) - Auslöser für Beschaffung
|
|
|
|
Invarianten:
|
|
- Bestandsmenge = SUMME(Chargen.Menge wo Status = VERFUEGBAR)
|
|
- Kann nicht mehr abheben als verfügbar
|
|
- FEFO durchgesetzt: Ältestes Verfallsdatum zuerst
|
|
- Negativer Bestand nicht erlaubt
|
|
- Chargen mit Verfallsdatum < HEUTE müssen Status = ABGELAUFEN haben
|
|
```
|
|
|
|
**Geschäftsmethoden:**
|
|
```java
|
|
// Bestand hinzufügen
|
|
public Result<BestandFehler, Void> hinzufuegen(
|
|
ChargenId chargenId,
|
|
ChargenTyp chargenTyp,
|
|
Menge menge,
|
|
LocalDate verfallsdatum
|
|
);
|
|
|
|
// Bestand abheben (mit FEFO)
|
|
public Result<BestandFehler, List<ChargenAbhebung>> abheben(Menge menge);
|
|
|
|
// Charge reservieren
|
|
public Result<BestandFehler, Void> reservieren(ChargenId chargenId, Menge menge);
|
|
|
|
// Abgelaufene Chargen markieren
|
|
public Result<BestandFehler, List<ChargenId>> markiereAbgelaufeneChargen();
|
|
|
|
// Abfragemethoden
|
|
public Menge verfuegbareMenge();
|
|
public List<Charge> findeChargenNachFEFO(); // Sortiert nach Verfallsdatum
|
|
public boolean istUnterMinimalBestand();
|
|
```
|
|
|
|
**Domänen-Events:**
|
|
```java
|
|
BestandUnterMinimum(ArtikelId, FilialId, Menge)
|
|
ChargeLaeuftDemnaechstAb(ChargenId, ArtikelId, Verfallsdatum)
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Bestandsbewegung (Aggregate Root)
|
|
|
|
**Event Sourcing Kandidat!**
|
|
|
|
**Struktur:**
|
|
```
|
|
Bestandsbewegung (Aggregate Root)
|
|
├── BestandsbewegungId (Wertobjekt)
|
|
├── ArtikelId (Wertobjekt)
|
|
├── ChargenId (Wertobjekt) - KRITISCH für Rückverfolgbarkeit
|
|
├── BewegungsTyp (Wertobjekt: WARENEINGANG | PRODUKTIONSAUSGANG | VERKAUF |
|
|
│ INTERFILIAL_TRANSFER | AUSSCHUSS | INVENTUR_KORREKTUR)
|
|
├── VonLagerort (Wertobjekt: LagerortId) - Null bei WARENEINGANG
|
|
├── ZuLagerort (Wertobjekt: LagerortId) - Null bei VERKAUF/AUSSCHUSS
|
|
├── VonFiliale (Wertobjekt: FilialId)
|
|
├── ZuFiliale (Wertobjekt: FilialId) - Für Interfilial-Transfers
|
|
├── Menge (Wertobjekt)
|
|
├── Bewegungsdatum (Wertobjekt: Zeitstempel)
|
|
├── DurchgefuehrtVon (Wertobjekt: BenutzerId)
|
|
├── Grund (Wertobjekt: für AUSSCHUSS/KORREKTUR)
|
|
├── Referenzdokument (Wertobjekt) - WareneingangId, ProduktionsauftragId, RechnungId
|
|
└── Rueckverfolgbarkeitskette (Wertobjekt) - Link zu vorgelagerten/nachgelagerten Bewegungen
|
|
|
|
Invarianten:
|
|
- Menge muss positiv sein
|
|
- WARENEINGANG: VonLagerort muss null sein
|
|
- VERKAUF oder AUSSCHUSS: ZuLagerort muss null sein
|
|
- INTERFILIAL_TRANSFER: VonFiliale != ZuFiliale
|
|
- AUSSCHUSS benötigt Grund
|
|
- Alle Bewegungen müssen eine Charge referenzieren
|
|
```
|
|
|
|
**Domänen-Events:**
|
|
```java
|
|
BestandsbewegungErfasst(BestandsbewegungId, ChargenId, BewegungsTyp)
|
|
```
|
|
|
|
---
|
|
|
|
## Repository-Schnittstellen
|
|
|
|
```java
|
|
public interface BestandRepository {
|
|
Result<RepositoryFehler, Void> speichern(Bestand bestand);
|
|
Result<RepositoryFehler, Bestand> findeNachArtikelUndLagerort(
|
|
ArtikelId artikelId,
|
|
LagerortId lagerortId,
|
|
FilialId filialId
|
|
);
|
|
Result<RepositoryFehler, List<Bestand>> findeUnterMinimalBestand();
|
|
}
|
|
|
|
public interface BestandsbewegungRepository {
|
|
Result<RepositoryFehler, Void> speichern(Bestandsbewegung bewegung);
|
|
Result<RepositoryFehler, List<Bestandsbewegung>> findeNachCharge(ChargenId chargenId);
|
|
Result<RepositoryFehler, List<Bestandsbewegung>> findeNachZeitraum(
|
|
LocalDate von, LocalDate bis
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rückverfolgbarkeits-Beispiel
|
|
|
|
```java
|
|
// Beispiel: Rückruf wegen kontaminierter Rohstoff-Charge
|
|
|
|
// 1. Finde alle Bestandsbewegungen für Lieferanten-Charge
|
|
List<Bestandsbewegung> wareneingang = repository.findeNachCharge(
|
|
ChargenId.von("SUPPLIER-BATCH-12345")
|
|
);
|
|
|
|
// 2. Finde Produktionschargen, die diesen Rohstoff verwendet haben
|
|
List<ChargenId> produktionschargen = wareneingang.stream()
|
|
.filter(b -> b.bewegungsTyp() == BewegungsTyp.PRODUKTIONSAUSGANG)
|
|
.map(b -> b.referenzdokument().produktionsChargenId())
|
|
.toList();
|
|
|
|
// 3. Finde alle Verkäufe dieser Produktionschargen
|
|
List<Bestandsbewegung> verkaeufe = produktionschargen.stream()
|
|
.flatMap(chargeId -> repository.findeNachCharge(chargeId).stream())
|
|
.filter(b -> b.bewegungsTyp() == BewegungsTyp.VERKAUF)
|
|
.toList();
|
|
|
|
// 4. Extrahiere betroffene Kunden/Rechnungen
|
|
List<RechnungId> betroffeneRechnungen = verkaeufe.stream()
|
|
.map(b -> b.referenzdokument().rechnungId())
|
|
.toList();
|
|
|
|
// → Rückruf kann durchgeführt werden!
|
|
```
|