diff --git a/.gitignore b/.gitignore
index 747d42e..09c5d49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -301,3 +301,10 @@ TSWLatexianTemp*
# Uncomment the next line to have this generated file ignored.
#*Notes.bib
+# ---> Java / Maven
+target/
+*.class
+
+# ---> JetBrains (IntelliJ IDEA)
+.idea/
+*.iml
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/SE1_Team_2.iml b/.idea/SE1_Team_2.iml
deleted file mode 100644
index d6ebd48..0000000
--- a/.idea/SE1_Team_2.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
deleted file mode 100644
index d43d322..0000000
--- a/.idea/checkstyle-idea.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- 12.3.1
- JavaOnly
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index d96e79f..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
deleted file mode 100644
index aa00ffa..0000000
--- a/.idea/encodings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 712ab9d..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index e122dea..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/model/Kunde.java b/src/main/java/de/hsmannheim/faktura/kunde/model/Kunde.java
new file mode 100644
index 0000000..7ff3c19
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/model/Kunde.java
@@ -0,0 +1,91 @@
+package de.hsmannheim.faktura.kunde.model;
+
+import java.util.Objects;
+
+/**
+ * Datenobjekt "Kunde" gemäß Pflichtenheft Kap. 6.1.2.
+ *
+ * Reiner Datentyp (POJO) ohne fachliche Validierung – die Prüfung von
+ * Pflichtfeldern, der Bedingung firmenname||nachname und des E-Mail-Formats
+ * erfolgt in Phase 3 im KundeService, nicht in dieser Klasse.
+ */
+public class Kunde {
+
+ private long kundeId; // Pflicht: fortlaufender, eindeutiger Primärschlüssel
+ private String firmenname; // Pflicht*: sofern kein Nachname (Constraint in Phase 3)
+ private String nachname; // Pflicht*: sofern kein Firmenname
+ private String vorname; // Optional
+ private String strasse; // Pflicht: Straße + Hausnummer
+ private String plz; // Pflicht: String, um führende Nullen zu erhalten!
+ private String ort; // Pflicht
+ private String telefon; // Optional
+ private String email; // Optional: Formatprüfung in Phase 3
+ private String ustIdNr; // Optional
+ private String lieferadresse; // Optional: abweichende Lieferadresse
+ private String ansprechpartner; // Optional
+
+ /** No-arg-Konstruktor – von Jackson für die JSON-Deserialisierung benötigt (P1-3). */
+ public Kunde() {
+ }
+
+ // --- Getter und Setter (P1-2) ---
+
+ public long getKundeId() { return kundeId; }
+ public void setKundeId(long kundeId) { this.kundeId = kundeId; }
+
+ public String getFirmenname() { return firmenname; }
+ public void setFirmenname(String firmenname) { this.firmenname = firmenname; }
+
+ public String getNachname() { return nachname; }
+ public void setNachname(String nachname) { this.nachname = nachname; }
+
+ public String getVorname() { return vorname; }
+ public void setVorname(String vorname) { this.vorname = vorname; }
+
+ public String getStrasse() { return strasse; }
+ public void setStrasse(String strasse) { this.strasse = strasse; }
+
+ public String getPlz() { return plz; }
+ public void setPlz(String plz) { this.plz = plz; }
+
+ public String getOrt() { return ort; }
+ public void setOrt(String ort) { this.ort = ort; }
+
+ public String getTelefon() { return telefon; }
+ public void setTelefon(String telefon) { this.telefon = telefon; }
+
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+
+ public String getUstIdNr() { return ustIdNr; }
+ public void setUstIdNr(String ustIdNr) { this.ustIdNr = ustIdNr; }
+
+ public String getLieferadresse() { return lieferadresse; }
+ public void setLieferadresse(String lieferadresse) { this.lieferadresse = lieferadresse; }
+
+ public String getAnsprechpartner() { return ansprechpartner; }
+ public void setAnsprechpartner(String ansprechpartner) { this.ansprechpartner = ansprechpartner; }
+
+ // --- Identität über den Primärschlüssel kundeId (P1-2) ---
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Kunde other = (Kunde) o;
+ return kundeId == other.kundeId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(kundeId);
+ }
+
+ @Override
+ public String toString() {
+ return "Kunde{kundeId=" + kundeId
+ + ", firmenname='" + firmenname + '\''
+ + ", nachname='" + nachname + '\''
+ + ", ort='" + ort + '\'' + '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeContainer.java b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeContainer.java
new file mode 100644
index 0000000..1c2bced
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeContainer.java
@@ -0,0 +1,24 @@
+package de.hsmannheim.faktura.kunde.repository;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reiner Persistenz-Container: bündelt die gespeicherten Kunden und den
+ * fortlaufenden ID-Zähler in einem JSON-Objekt. Der mitgespeicherte Zähler
+ * garantiert, dass vergebene IDs nach einem Neustart nicht wiederverwendet
+ * werden (P2-3).
+ */
+public class KundeContainer {
+
+ private List kunden = new ArrayList<>();
+ private long naechsteId = 1;
+
+ public List getKunden() { return kunden; }
+ public void setKunden(List kunden) { this.kunden = kunden; }
+
+ public long getNaechsteId() { return naechsteId; }
+ public void setNaechsteId(long naechsteId) { this.naechsteId = naechsteId; }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepository.java b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepository.java
new file mode 100644
index 0000000..2c0e58a
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepository.java
@@ -0,0 +1,22 @@
+package de.hsmannheim.faktura.kunde.repository;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Abstrakte Datenhaltung für Kunden (IF-02, NF-ARCH-02).
+ * Kapselt die Speichertechnologie, sodass sie austauschbar ist.
+ */
+public interface KundeRepository {
+
+ /** Legt neu an (kundeId == 0) oder aktualisiert; liefert die (ggf. neu vergebene) kundeId. */
+ long speichern(Kunde kunde);
+
+ Optional findeById(long kundeId);
+
+ List findeAlle();
+
+ void loeschen(long kundeId);
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJson.java b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJson.java
new file mode 100644
index 0000000..f7fbdc5
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJson.java
@@ -0,0 +1,89 @@
+package de.hsmannheim.faktura.kunde.repository;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import de.hsmannheim.faktura.kunde.model.Kunde;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * JSON-basierte Umsetzung von KundeRepository (NF-ARCH-01).
+ * Hält die Kunden im Speicher und schreibt nach jeder Änderung in eine
+ * lokale JSON-Datei. Beim Erzeugen wird eine vorhandene Datei geladen.
+ */
+public class KundeRepositoryJson implements KundeRepository {
+
+ private final File datei;
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private final Map kunden = new LinkedHashMap<>();
+ private long naechsteId = 1;
+
+ public KundeRepositoryJson(File datei) {
+ this.datei = datei;
+ laden();
+ }
+
+ @Override
+ public long speichern(Kunde kunde) {
+ if (kunde.getKundeId() == 0) {
+ // Neuanlage: nächste freie ID vergeben, Zähler hochzählen (kein Reuse)
+ kunde.setKundeId(naechsteId);
+ naechsteId++;
+ }
+ kunden.put(kunde.getKundeId(), kunde);
+ speichernInDatei();
+ return kunde.getKundeId();
+ }
+
+ @Override
+ public Optional findeById(long kundeId) {
+ return Optional.ofNullable(kunden.get(kundeId));
+ }
+
+ @Override
+ public List findeAlle() {
+ return new ArrayList<>(kunden.values());
+ }
+
+ @Override
+ public void loeschen(long kundeId) {
+ kunden.remove(kundeId);
+ speichernInDatei();
+ }
+
+ // --- interne Persistenz-Hilfen ---
+
+ private void laden() {
+ if (!datei.exists()) {
+ return; // erster Start: leeres Repository
+ }
+ try {
+ KundeContainer container = mapper.readValue(datei, KundeContainer.class);
+ kunden.clear();
+ for (Kunde k : container.getKunden()) {
+ kunden.put(k.getKundeId(), k);
+ }
+ naechsteId = container.getNaechsteId();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Kundendaten konnten nicht geladen werden: " + datei, e);
+ }
+ }
+
+ private void speichernInDatei() {
+ try {
+ KundeContainer container = new KundeContainer();
+ container.setKunden(new ArrayList<>(kunden.values()));
+ container.setNaechsteId(naechsteId);
+ mapper.writerWithDefaultPrettyPrinter().writeValue(datei, container);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Kundendaten konnten nicht gespeichert werden: " + datei, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/service/BelegReferenzPruefer.java b/src/main/java/de/hsmannheim/faktura/kunde/service/BelegReferenzPruefer.java
new file mode 100644
index 0000000..6477411
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/service/BelegReferenzPruefer.java
@@ -0,0 +1,12 @@
+package de.hsmannheim.faktura.kunde.service;
+
+/**
+ * Schmale Abstraktion über die Belegseite (Gruppe F) für die GR-05-Löschsperre.
+ * Liefert true, wenn der Kunde in mindestens einem Beleg referenziert ist.
+ * Die echte Umsetzung liefert Gruppe F (BelegRepository, OP-1/P0-4); bis dahin
+ * und in Tests wird gegen dieses Interface programmiert.
+ */
+@FunctionalInterface
+public interface BelegReferenzPruefer {
+ boolean istKundeReferenziert(long kundeId);
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/service/KundeService.java b/src/main/java/de/hsmannheim/faktura/kunde/service/KundeService.java
new file mode 100644
index 0000000..eddc987
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/service/KundeService.java
@@ -0,0 +1,89 @@
+package de.hsmannheim.faktura.kunde.service;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+import de.hsmannheim.faktura.kunde.repository.KundeRepository;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Fachliche Logik der Kundenverwaltung (F-KV-01…04 / BA-KV-01…04).
+ * Orchestriert Validierung (Phase 3) und Persistenz (Phase 2); enthält
+ * selbst keine Speicher- oder Prüf-Details (NF-MAINT-03, Schichtentrennung).
+ * Die vier öffentlichen Methoden entsprechen exakt dem Schnittstellenvertrag K1.
+ */
+public class KundeService {
+
+ private final KundeRepository repository;
+ private final KundeValidator validator;
+ private final BelegReferenzPruefer belegPruefer;
+
+ public KundeService(KundeRepository repository,
+ KundeValidator validator,
+ BelegReferenzPruefer belegPruefer) {
+ this.repository = repository;
+ this.validator = validator;
+ this.belegPruefer = belegPruefer;
+ }
+
+ /** P4-1 · F-KV-01 / BA-KV-01: validieren, ID vergeben lassen, persistieren. */
+ public long anlegen(Kunde kunde) {
+ validator.pruefe(kunde); // wirft bei Verstoß -> speichern wird nie erreicht (P3-4)
+ kunde.setKundeId(0); // Neuanlage erzwingen: Repository vergibt die nächste ID
+ return repository.speichern(kunde);
+ }
+
+ /** P4-2 · F-KV-02 / BA-KV-02: vorhandenen Kunden laden, validieren, persistieren. */
+ public void bearbeiten(Kunde kunde) {
+ if (repository.findeById(kunde.getKundeId()).isEmpty()) {
+ throw new ValidierungsException(
+ "Kunde mit ID " + kunde.getKundeId() + " existiert nicht.");
+ }
+ validator.pruefe(kunde);
+ repository.speichern(kunde);
+ }
+
+ /** P4-3 · F-KV-03 / BA-KV-03: Treffer über Firmenname/Nachname UND Kundennummer. */
+ public List suchen(String suchbegriff) {
+ if (suchbegriff == null || suchbegriff.isBlank()) {
+ return List.of();
+ }
+ String begriff = suchbegriff.trim().toLowerCase();
+ return repository.findeAlle().stream()
+ .filter(k -> passt(k, begriff))
+ .collect(Collectors.toList());
+ }
+
+ /** P5-1 · K3: Kundenauflösung per ID für Gruppe F (Ersatz für deren FakeCustomerLookup). */
+ public Optional finde(long kundeId) {
+ return repository.findeById(kundeId);
+ }
+
+ /** P4-4 / P4-5 · F-KV-04 / BA-KV-04 / GR-05 / NF-SEC-02: nur löschen, wenn unreferenziert. */
+ public void loeschen(long kundeId) {
+ Optional vorhanden = repository.findeById(kundeId);
+ if (vorhanden.isEmpty()) {
+ throw new ValidierungsException(
+ "Kunde mit ID " + kundeId + " existiert nicht.");
+ }
+ if (belegPruefer.istKundeReferenziert(kundeId)) {
+ throw new LoeschsperreException(
+ "Kunde " + kundeId + " ist in mindestens einem Beleg referenziert "
+ + "und darf nicht gelöscht werden (GR-05).");
+ }
+ repository.loeschen(kundeId);
+ }
+
+ // --- interne Suchhilfen ---
+
+ private boolean passt(Kunde k, String begriff) {
+ if (enthaelt(k.getFirmenname(), begriff)) return true;
+ if (enthaelt(k.getNachname(), begriff)) return true;
+ return String.valueOf(k.getKundeId()).equals(begriff); // Suche über Kundennummer
+ }
+
+ private boolean enthaelt(String feld, String begriff) {
+ return feld != null && feld.toLowerCase().contains(begriff);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/service/KundeValidator.java b/src/main/java/de/hsmannheim/faktura/kunde/service/KundeValidator.java
new file mode 100644
index 0000000..a774c6e
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/service/KundeValidator.java
@@ -0,0 +1,52 @@
+package de.hsmannheim.faktura.kunde.service;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+
+import java.util.regex.Pattern;
+
+/**
+ * Prüft einen Kunden gegen die fachlichen Regeln aus Pflichtenheft Kap. 6.1.2.
+ * Wirft bei Verstoß eine ValidierungsException – der aufrufende Service
+ * persistiert dann nicht (P3-4).
+ */
+public class KundeValidator {
+
+ // Pragmatische Prüfung: etwas vor dem @, etwas danach, ein Punkt mit Endung.
+ // Lehnt z. B. "kunde@" ab (MT-KV-04), ohne RFC-Vollständigkeit zu erzwingen.
+ private static final Pattern EMAIL_MUSTER =
+ Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
+
+ public void pruefe(Kunde kunde) {
+ if (kunde == null) {
+ throw new ValidierungsException("Kunde darf nicht null sein.");
+ }
+
+ // P3-2: mindestens Firmenname ODER Nachname
+ if (istLeer(kunde.getFirmenname()) && istLeer(kunde.getNachname())) {
+ throw new ValidierungsException(
+ "Entweder Firmenname oder Nachname muss angegeben sein.");
+ }
+
+ // P3-1: Pflichtfelder der Adresse
+ if (istLeer(kunde.getStrasse())) {
+ throw new ValidierungsException("Straße ist ein Pflichtfeld.");
+ }
+ if (istLeer(kunde.getPlz())) {
+ throw new ValidierungsException("PLZ ist ein Pflichtfeld.");
+ }
+ if (istLeer(kunde.getOrt())) {
+ throw new ValidierungsException("Ort ist ein Pflichtfeld.");
+ }
+
+ // P3-3: E-Mail nur prüfen, wenn überhaupt angegeben
+ if (!istLeer(kunde.getEmail())
+ && !EMAIL_MUSTER.matcher(kunde.getEmail()).matches()) {
+ throw new ValidierungsException(
+ "E-Mail-Adresse hat kein gültiges Format: " + kunde.getEmail());
+ }
+ }
+
+ private boolean istLeer(String wert) {
+ return wert == null || wert.isBlank();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/service/LoeschsperreException.java b/src/main/java/de/hsmannheim/faktura/kunde/service/LoeschsperreException.java
new file mode 100644
index 0000000..c0c8c09
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/service/LoeschsperreException.java
@@ -0,0 +1,11 @@
+package de.hsmannheim.faktura.kunde.service;
+
+/**
+ * Wird geworfen, wenn ein Kunde wegen Beleg-Referenz (GR-05) nicht
+ * gelöscht werden darf.
+ */
+public class LoeschsperreException extends RuntimeException {
+ public LoeschsperreException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/hsmannheim/faktura/kunde/service/ValidierungsException.java b/src/main/java/de/hsmannheim/faktura/kunde/service/ValidierungsException.java
new file mode 100644
index 0000000..93dc8ab
--- /dev/null
+++ b/src/main/java/de/hsmannheim/faktura/kunde/service/ValidierungsException.java
@@ -0,0 +1,13 @@
+package de.hsmannheim.faktura.kunde.service;
+
+/**
+ * Wird geworfen, wenn ein Kunde gegen eine fachliche Validierungsregel
+ * verstößt (Pflichtenheft Kap. 6.1.2). Unchecked, damit die im
+ * Schnittstellenvertrag K1 festgelegten Service-Signaturen ohne
+ * throws-Klausel auskommen.
+ */
+public class ValidierungsException extends RuntimeException {
+ public ValidierungsException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJsonTest.java b/src/test/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJsonTest.java
new file mode 100644
index 0000000..cdf2f76
--- /dev/null
+++ b/src/test/java/de/hsmannheim/faktura/kunde/repository/KundeRepositoryJsonTest.java
@@ -0,0 +1,40 @@
+package de.hsmannheim.faktura.kunde.repository;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class KundeRepositoryJsonTest {
+
+ @Test
+ void persistenz_ueberlebt_neustart(@TempDir Path tempDir) {
+ File datei = tempDir.resolve("kunden.json").toFile();
+
+ // 1. Repository anlegen und einen Kunden speichern
+ KundeRepository repo1 = new KundeRepositoryJson(datei);
+ Kunde kunde = new Kunde();
+ kunde.setNachname("Mustermann");
+ kunde.setStrasse("Hauptstr. 1");
+ kunde.setPlz("01067"); // führende Null!
+ kunde.setOrt("Dresden");
+ long id = repo1.speichern(kunde);
+
+ // 2. Neues Repository auf dieselbe Datei = simulierter Neustart
+ KundeRepository repo2 = new KundeRepositoryJson(datei);
+ Optional geladen = repo2.findeById(id);
+
+ // 3. Kunde ist unverändert wiederhergestellt
+ assertTrue(geladen.isPresent(), "Kunde sollte nach Neustart vorhanden sein");
+ assertEquals("Mustermann", geladen.get().getNachname());
+ assertEquals("01067", geladen.get().getPlz(),
+ "Führende Null der PLZ muss erhalten bleiben");
+ assertEquals("Dresden", geladen.get().getOrt());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/de/hsmannheim/faktura/kunde/service/KundeServiceTest.java b/src/test/java/de/hsmannheim/faktura/kunde/service/KundeServiceTest.java
new file mode 100644
index 0000000..c58e6f5
--- /dev/null
+++ b/src/test/java/de/hsmannheim/faktura/kunde/service/KundeServiceTest.java
@@ -0,0 +1,99 @@
+package de.hsmannheim.faktura.kunde.service;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+import de.hsmannheim.faktura.kunde.repository.KundeRepository;
+import de.hsmannheim.faktura.kunde.repository.KundeRepositoryJson;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class KundeServiceTest {
+
+ private KundeService service;
+ private boolean kundeIstReferenziert; // steuert den BelegReferenzPruefer im Test
+
+ @BeforeEach
+ void setUp(@TempDir Path tempDir) {
+ File datei = tempDir.resolve("kunden.json").toFile();
+ KundeRepository repository = new KundeRepositoryJson(datei);
+ KundeValidator validator = new KundeValidator();
+ BelegReferenzPruefer belegPruefer = kundeId -> kundeIstReferenziert;
+ service = new KundeService(repository, validator, belegPruefer);
+ kundeIstReferenziert = false;
+ }
+
+ private Kunde gueltigerKunde() {
+ Kunde k = new Kunde();
+ k.setNachname("Mustermann");
+ k.setStrasse("Hauptstr. 1");
+ k.setPlz("68159");
+ k.setOrt("Mannheim");
+ return k;
+ }
+
+ @Test
+ void anlegen_vergibtIdUndPersistiert() { // BA-KV-01
+ long id = service.anlegen(gueltigerKunde());
+ assertTrue(id > 0);
+ assertEquals(1, service.suchen("Mustermann").size());
+ }
+
+ @Test
+ void anlegen_ungueltig_wirdNichtGespeichert() { // P3-4 im Service
+ Kunde k = gueltigerKunde();
+ k.setNachname(null);
+ assertThrows(ValidierungsException.class, () -> service.anlegen(k));
+ assertTrue(service.suchen("Mustermann").isEmpty());
+ }
+
+ @Test
+ void bearbeiten_aendertGespeichertenKunden() { // BA-KV-02
+ service.anlegen(gueltigerKunde());
+ Kunde k = service.suchen("Mustermann").get(0);
+ k.setTelefon("0621 12345");
+ service.bearbeiten(k);
+ assertEquals("0621 12345", service.suchen("Mustermann").get(0).getTelefon());
+ }
+
+ @Test
+ void suchen_ueberKundennummer_findet() { // BA-KV-03 / MT-KV-08
+ long id = service.anlegen(gueltigerKunde());
+ assertEquals(1, service.suchen(String.valueOf(id)).size());
+ }
+
+ @Test
+ void suchen_ohneTreffer_istLeer() { // BA-KV-03 / MT-KV-09
+ service.anlegen(gueltigerKunde());
+ assertTrue(service.suchen("Gibtsnicht").isEmpty());
+ }
+
+ @Test
+ void loeschen_nichtReferenziert_entfernt() { // BA-KV-04 / MT-KV-10
+ long id = service.anlegen(gueltigerKunde());
+ kundeIstReferenziert = false;
+ service.loeschen(id);
+ assertTrue(service.suchen("Mustermann").isEmpty());
+ }
+
+ @Test
+ void loeschen_referenziert_wirdAbgewiesen() { // BA-KV-04 / GR-05 / MT-KV-11
+ long id = service.anlegen(gueltigerKunde());
+ kundeIstReferenziert = true;
+ assertThrows(LoeschsperreException.class, () -> service.loeschen(id));
+ assertEquals(1, service.suchen("Mustermann").size()); // noch vorhanden
+ }
+
+ @Test
+ void finde_liefertAngelegtenKunden() { // K3
+ long id = service.anlegen(gueltigerKunde());
+ assertTrue(service.finde(id).isPresent());
+ assertEquals(id, service.finde(id).get().getKundeId());
+ assertTrue(service.finde(999L).isEmpty()); // unbekannte ID -> leer
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/de/hsmannheim/faktura/kunde/service/KundeValidatorTest.java b/src/test/java/de/hsmannheim/faktura/kunde/service/KundeValidatorTest.java
new file mode 100644
index 0000000..bf2395e
--- /dev/null
+++ b/src/test/java/de/hsmannheim/faktura/kunde/service/KundeValidatorTest.java
@@ -0,0 +1,54 @@
+package de.hsmannheim.faktura.kunde.service;
+
+import de.hsmannheim.faktura.kunde.model.Kunde;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class KundeValidatorTest {
+
+ private final KundeValidator validator = new KundeValidator();
+
+ private Kunde gueltigerKunde() {
+ Kunde k = new Kunde();
+ k.setNachname("Mustermann");
+ k.setStrasse("Hauptstr. 1");
+ k.setPlz("68159");
+ k.setOrt("Mannheim");
+ return k;
+ }
+
+ @Test
+ void gueltigerKunde_wirftNicht() {
+ assertDoesNotThrow(() -> validator.pruefe(gueltigerKunde()));
+ }
+
+ @Test
+ void ohneFirmennameUndNachname_wirdAbgelehnt() { // → MT-KV-02
+ Kunde k = gueltigerKunde();
+ k.setNachname(null);
+ assertThrows(ValidierungsException.class, () -> validator.pruefe(k));
+ }
+
+ @Test
+ void ohneStrasse_wirdAbgelehnt() { // → MT-KV-03
+ Kunde k = gueltigerKunde();
+ k.setStrasse(null);
+ assertThrows(ValidierungsException.class, () -> validator.pruefe(k));
+ }
+
+ @Test
+ void ungueltigeEmail_wirdAbgelehnt() { // → MT-KV-04
+ Kunde k = gueltigerKunde();
+ k.setEmail("kunde@");
+ assertThrows(ValidierungsException.class, () -> validator.pruefe(k));
+ }
+
+ @Test
+ void gueltigeEmail_wirftNicht() {
+ Kunde k = gueltigerKunde();
+ k.setEmail("kunde@example.com");
+ assertDoesNotThrow(() -> validator.pruefe(k));
+ }
+}