diff --git a/data/products.json b/data/products.json new file mode 100644 index 0000000..e4001c2 --- /dev/null +++ b/data/products.json @@ -0,0 +1,8 @@ +[ { + "produktId" : "P-0001", + "bezeichnung" : "M6 Schraube", + "einzelpreisNetto" : 10.50, + "mwstSatz" : "NORMAL", + "beschreibung" : "Beschreibung", + "kategorie" : "Werkzeug" +} ] \ No newline at end of file diff --git a/src/main/java/de/hsmannheim/faktura/produkt/MwstSatz.java b/src/main/java/de/hsmannheim/faktura/produkt/MwstSatz.java new file mode 100644 index 0000000..8b7f93e --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/produkt/MwstSatz.java @@ -0,0 +1,8 @@ +package de.hsmannheim.faktura.produkt; + +public enum MwstSatz { + + NORMAL, + ERMAESSIGT +} + diff --git a/src/main/java/de/hsmannheim/faktura/produkt/Produkt.java b/src/main/java/de/hsmannheim/faktura/produkt/Produkt.java new file mode 100644 index 0000000..0618dff --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/produkt/Produkt.java @@ -0,0 +1,91 @@ +package de.hsmannheim.faktura.produkt; + +import java.math.BigDecimal; + +public class Produkt { + + private String produktId; + private String bezeichnung; + private BigDecimal einzelpreisNetto; + private MwstSatz mwstSatz; + private String beschreibung; + private String kategorie; + + public Produkt() { + + } + + public Produkt(String produktId, String bezeichnung, BigDecimal einzelpreisNetto, + MwstSatz mwstSatz, String beschreibung, String kategorie) { + this.produktId = produktId; + this.bezeichnung = bezeichnung; + this.einzelpreisNetto = einzelpreisNetto; + this.mwstSatz = mwstSatz; + this.beschreibung = beschreibung; + this.kategorie = kategorie; + } + + // ========================================== + // Getter und Setter + // ========================================== + + public String getProduktId() { + return produktId; + } + + public void setProduktId(String produktId) { + this.produktId = produktId; + } + + public String getBezeichnung() { + return bezeichnung; + } + + public void setBezeichnung(String bezeichnung) { + this.bezeichnung = bezeichnung; + } + + public BigDecimal getEinzelpreisNetto() { + return einzelpreisNetto; + } + + public void setEinzelpreisNetto(BigDecimal einzelpreisNetto) { + this.einzelpreisNetto = einzelpreisNetto; + } + + public MwstSatz getMwstSatz() { + return mwstSatz; + } + + public void setMwstSatz(MwstSatz mwstSatz) { + this.mwstSatz = mwstSatz; + } + + public String getBeschreibung() { + return beschreibung; + } + + public void setBeschreibung(String beschreibung) { + this.beschreibung = beschreibung; + } + + public String getKategorie() { + return kategorie; + } + + public void setKategorie(String kategorie) { + this.kategorie = kategorie; + } + + @Override + public String toString() { + return "Produkt{" + + "produktId='" + produktId + '\'' + + ", bezeichnung='" + bezeichnung + '\'' + + ", einzelpreisNetto=" + einzelpreisNetto + + ", mwstSatz=" + mwstSatz + + ", kategorie='" + kategorie + '\'' + + '}'; + } +} + diff --git a/src/main/java/de/hsmannheim/faktura/produkt/repository/ProduktRepository.java b/src/main/java/de/hsmannheim/faktura/produkt/repository/ProduktRepository.java new file mode 100644 index 0000000..80f2f49 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/produkt/repository/ProduktRepository.java @@ -0,0 +1,101 @@ +package de.hsmannheim.faktura.produkt.repository; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import de.hsmannheim.faktura.produkt.Produkt; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + + +public class ProduktRepository { + + + private static final String FILE_PATH = "./data/products.json"; + private final ObjectMapper objectMapper; + private List cache; + + public ProduktRepository() { + this.objectMapper = new ObjectMapper(); + this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + this.cache = new ArrayList<>(); + + + loadData(); + } + + + public List findAll() { + return new ArrayList<>(cache); + } + + + public Optional findById(String produktId) { + return cache.stream() + .filter(p -> p.getProduktId().equals(produktId)) + .findFirst(); + } + + public void save(Produkt product) { + + Optional existingProduct = findById(product.getProduktId()); + + if (existingProduct.isPresent()) { + int index = cache.indexOf(existingProduct.get()); + cache.set(index, product); + } else { + cache.add(product); + } + + + saveData(); + } + + + public boolean deleteById(String produktId) { + boolean removed = cache.removeIf(p -> p.getProduktId().equals(produktId)); + if (removed) { + saveData(); + } + return removed; + } + + + private void loadData() { + File file = new File(FILE_PATH); + + + if (!file.exists()) { + this.cache = new ArrayList<>(); + return; + } + + try { + this.cache = objectMapper.readValue(file, new TypeReference>() {}); + } catch (IOException e) { + System.err.println("Fehler beim Laden der Produktdaten: " + e.getMessage()); + + this.cache = new ArrayList<>(); + } + } + + + private void saveData() { + File file = new File(FILE_PATH); + + File directory = file.getParentFile(); + if (directory != null && !directory.exists()) { + directory.mkdirs(); + } + + try { + objectMapper.writeValue(file, cache); + } catch (IOException e) { + System.err.println("Fehler beim Speichern der Produktdaten: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/hsmannheim/faktura/produkt/service/ProduktService.java b/src/main/java/de/hsmannheim/faktura/produkt/service/ProduktService.java new file mode 100644 index 0000000..f496af9 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/produkt/service/ProduktService.java @@ -0,0 +1,64 @@ +package de.hsmannheim.faktura.produkt.service; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +import de.hsmannheim.faktura.produkt.Produkt; +import de.hsmannheim.faktura.produkt.repository.ProduktRepository; + + +public class ProduktService { + + private final ProduktRepository produktRepository; + + public ProduktService(ProduktRepository produktRepository) { + this.produktRepository = produktRepository; + } + + + public List getAllProducts() { + return produktRepository.findAll(); + } + + + public Optional getProductById(String produktId) { + return produktRepository.findById(produktId); + } + + + public void saveProduct(Produkt product) { + if (product == null) { + throw new IllegalArgumentException("Das Produkt darf nicht null sein."); + } + + if (product.getEinzelpreisNetto() == null || product.getEinzelpreisNetto().compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Anforderung F-SH-PV-06 verletzt: Der Einzelpreis Netto darf nicht negativ sein."); + } + + if (product.getProduktId() == null || product.getProduktId().trim().isEmpty()) { + + product.setProduktId("PROD-" + java.util.UUID.randomUUID().toString().substring(0, 8)); + } + + produktRepository.save(product); + } + + public boolean deleteProduct(String produktId) { + if (produktId == null || produktId.trim().isEmpty()) { + return false; + } + + return produktRepository.deleteById(produktId); + } + + public List searchProducts(String query) { + if (query == null || query.trim().isEmpty()) { + return getAllProducts(); + } + String lowerQuery = query.toLowerCase(); + return getAllProducts().stream() + .filter(p -> p.getBezeichnung() != null && p.getBezeichnung().toLowerCase().contains(lowerQuery)) + .collect(java.util.stream.Collectors.toList()); +} +} \ No newline at end of file diff --git a/src/test/java/de/hsmannheim/faktura/produkt/service/ProduktServiceTest.java b/src/test/java/de/hsmannheim/faktura/produkt/service/ProduktServiceTest.java new file mode 100644 index 0000000..01fd6a1 --- /dev/null +++ b/src/test/java/de/hsmannheim/faktura/produkt/service/ProduktServiceTest.java @@ -0,0 +1,58 @@ +package de.hsmannheim.faktura.produkt.service; + +import de.hsmannheim.faktura.produkt.Produkt; +import de.hsmannheim.faktura.produkt.MwstSatz; +import de.hsmannheim.faktura.produkt.repository.ProduktRepository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProduktServiceTest { + + private ProduktService produktService; + private ProduktRepository produktRepository; + + @BeforeEach + public void setUp() { + produktRepository = new ProduktRepository(); + produktService = new ProduktService(produktRepository); + + // Cache leeren, damit alte JSON-Einträge die Tests nicht verfälschen + for (Produkt p : produktRepository.findAll()) { + produktRepository.deleteById(p.getProduktId()); + } + } + + @Test + public void testSaveValidProduct() { + Produkt valid = new Produkt("P-0001", "M6 Schraube", new BigDecimal("10.50"), MwstSatz.NORMAL, "Beschreibung", "Werkzeug"); + assertDoesNotThrow(() -> produktService.saveProduct(valid)); + assertTrue(produktService.getProductById("P-0001").isPresent()); + } + + @Test + public void testNegativePriceRejection() { + // AC-SH-PV-01: Preis kleiner als 0.00 muss abgelehnt werden + Produkt invalidProduct = new Produkt("P-9999", "Testprodukt", new BigDecimal("-1.00"), MwstSatz.NORMAL, "Test", "Test"); + + assertThrows(IllegalArgumentException.class, () -> produktService.saveProduct(invalidProduct)); + } + + @Test + public void testSearchCaseInsensitive() { + // AC-SH-PV-03: Suchtest für Groß-/Kleinschreibung + Produkt p1 = new Produkt("P-0001", "M6 Schraube", new BigDecimal("1.00"), MwstSatz.NORMAL, "", ""); + Produkt p2 = new Produkt("P-0002", "M8 SCHRAUBE", new BigDecimal("2.00"), MwstSatz.NORMAL, "", ""); + + produktService.saveProduct(p1); + produktService.saveProduct(p2); + + List results = produktService.searchProducts("schraube"); + assertEquals(2, results.size()); + } +} \ No newline at end of file