mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:19: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/
5.3 KiB
5.3 KiB
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:
// 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:
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:
BestandsbewegungErfasst(BestandsbewegungId, ChargenId, BewegungsTyp)
Repository-Schnittstellen
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
// 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!