Compare commits

..

80 Commits
master ... main

Author SHA1 Message Date
Christian Khazanovych d8572c4b2c Modultestplan von Team 3 2026-07-01 00:56:52 +02:00
Christian Khazanovych 03ec963e9c Modultestplan von Team 3 2026-07-01 00:56:41 +02:00
Christian Khazanovych b44925207f Dokumente/Modultestplan/README.md hinzugefügt 2026-07-01 00:56:01 +02:00
Christian Khazanovych fc64e17be9 Dokumente/Modultestbericht/README.md gelöscht 2026-07-01 00:29:38 +02:00
Christian Khazanovych d58e4b1aad Modultestbericht für die Fakturierungsanwendung 2026-07-01 00:29:33 +02:00
Christian Khazanovych 3341405540 Dokumente/Modultestbericht/README.md hinzugefügt 2026-07-01 00:28:32 +02:00
Christian Khazanovych 3da9d95488 README.md aktualisiert 2026-06-28 15:28:22 +02:00
Christian Khazanovych dcd9e2d733 README.md aktualisiert 2026-06-28 15:27:58 +02:00
Christian Khazanovych 1fcd1cf89c Merge branch 'main' of https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3 2026-06-25 19:52:17 +02:00
Christian Khazanovych 01c4cdb013 KundenVerwaltungTest: alle 13 Testfaelle implementiert und bestanden 2026-06-25 19:50:47 +02:00
Christian Khazanovych e4f7a2ebef sources_test.txt gelöscht 2026-06-25 00:31:29 +02:00
Christian Khazanovych 5fbf33a59a sources_main.txt gelöscht 2026-06-25 00:31:24 +02:00
Christian Khazanovych 2b5f3e54f5 Hilfsdateien entfernt und gitignore aktualisiert 2026-06-25 00:30:29 +02:00
Christian Khazanovych 9bdb6677f7 GR-04: Loeschsperre Tests korrigiert - alle 29 Tests bestanden 2026-06-25 00:29:20 +02:00
Christian Khazanovych 03155e9dd5 GR-04: Loeschsperre fuer referenzierte Kunden und Produkte vollstaendig implementiert 2026-06-25 00:21:29 +02:00
Christian Khazanovych 9815a352bf gitignore neu erstellt mit korrektem Encoding 2026-06-24 23:23:50 +02:00
Christian Khazanovych 4c4946ba1b out Ordner aus Repository entfernt 2026-06-24 23:22:55 +02:00
Christian Khazanovych 16c6b16132 README mit FlatLaf Befehlen aktualisiert 2026-06-24 19:15:56 +02:00
Christian Khazanovych 1a4ea905ae FlatLaf hinzugefuegt fuer bessere GUI auf macOS 2026-06-24 19:14:13 +02:00
Christian Khazanovych 6b9a992a68 Loeschfunktion fuer Mac synchronisiert 2026-06-24 18:50:09 +02:00
Christian Khazanovych e1753c4830 README hinzugefuegt 2026-06-24 18:12:47 +02:00
Christian Khazanovych 85d28c8920 README hinzugefuegt 2026-06-24 18:10:13 +02:00
Christian Khazanovych 41e39d0b79 data lib out endgueltig entfernt 2026-06-24 18:06:08 +02:00
Christian Khazanovych c6f5c2d2e8 vscode settings endgueltig entfernt 2026-06-24 18:05:10 +02:00
Christian Khazanovych 4a190914ca gitignore bereinigt 2026-06-24 18:04:21 +02:00
Christian Khazanovych 1cd04c4fe9 Gruppe I: Loeschfunktion fuer Kunden und Produkte hinzugefuegt 2026-06-24 17:58:48 +02:00
Christian Khazanovych 0968eea111 Gruppe I: Benutzeroberfläche hinzugefuegt 2026-06-24 17:48:42 +02:00
Christian Khazanovych 3c0f1cee07 Gruppe K: Belegworkflow Tests hinzugefuegt - 10/10 bestanden 2026-06-24 17:38:34 +02:00
Christian Khazanovych 3d74423278 Gruppe K: Belegworkflow hinzugefuegt 2026-06-24 17:34:16 +02:00
Christian Khazanovych a88ad8ec1b Gruppe L: Bestandsmanagement Tests hinzugefuegt - 10/10 bestanden 2026-06-24 17:24:52 +02:00
Christian Khazanovych ca66a4685d Gruppe L: Bestandsmanagement hinzugefuegt 2026-06-24 17:14:29 +02:00
Christian Khazanovych 8bc28a7b5e Unnoetige Ordner aus Repository entfernt 2026-06-24 16:44:51 +02:00
Christian Khazanovych 7ac1352cec Alle Dokumente in Ordnerstruktur organisiert 2026-06-24 16:39:51 +02:00
Christian Khazanovych fd8dfe6b2e :wq:wqMerge branch 'main' of https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3 2026-06-24 16:34:14 +02:00
Christian Khazanovych d44c0759e4 Pflichtenheft und Modultestplan Gruppe I 2026-06-15 16:39:41 +02:00
Christian Khazanovych b86f1d4b43 Pflichtenheft_Gruppe_I.md 2026-06-15 16:39:25 +02:00
Christian Khazanovych 7f21bede85 Pflichtenheft und Modultestplan Gruppe K 2026-06-15 15:06:13 +02:00
Christian Khazanovych 5b8a62b330 Pflichtenheft_Gruppe_K.md 2026-06-15 15:05:52 +02:00
Christian Khazanovych 252b47178c Pflichtenheft_Gruppe_J.md 2026-06-15 02:45:41 +02:00
Christian Khazanovych 83dc07dc7d Pflichtenheft und Modultestplan Gruppe J 2026-06-15 02:43:25 +02:00
Christian Khazanovych abd2765416 Pflichtenheft_Gruppe_L.md 2026-06-14 17:14:03 +02:00
Christian Khazanovych b06a9cb580 Pflichtenheft_Gruppe_L.md aktualisiert 2026-06-14 17:12:38 +02:00
Christian Khazanovych d28fac892a Markdown_Gruppe_J 2026-06-14 17:07:44 +02:00
Christian Khazanovych 264328aa5a Pflichtenheft und Modultestplan Gruppe L 2026-06-14 17:02:09 +02:00
Christian Khazanovych 237dfea547 Pflichtenheft_Gruppe_L.pdf gelöscht 2026-06-14 17:01:42 +02:00
Christian Khazanovych 41e77b12cb Dateien nach "/" hochladen
Pflichtenheft und Modultestplan Gruppe L
2026-06-14 17:01:06 +02:00
Christian Khazanovych 5d283add91 Aktualisierte Version des Lastenhefts nach Feedback 2026-06-10 17:41:04 +02:00
Christian Khazanovych 967a0b23f0 Lastenheft.pdf gelöscht 2026-06-10 17:40:03 +02:00
Christian Khazanovych fca50d60d0 Aktualisierte Version des Lastenhefts nach Feedback 2026-06-10 17:37:35 +02:00
Christian Khazanovych 2de29c492d Lastenheft.pdf gelöscht 2026-06-10 17:37:25 +02:00
Christian Khazanovych 08b0502a31 Aktualisierte Version des Lastenhefts nach Feedback 2026-06-10 17:37:00 +02:00
Christian Khazanovych 73010689b2 Dateien nach "/" hochladen
Aktualisierte Version des Lastenhefts nach Feedback
2026-06-10 17:36:34 +02:00
Christian Khazanovych 48361bbd90 Lastenheft.md aktualisiert 2026-06-10 17:33:48 +02:00
Christian Khazanovych e976fb3749 Lastenheft.md aktualisiert 2026-05-15 12:07:36 +02:00
Christian Khazanovych 08b9f31025 Lastenheft.md aktualisiert 2026-05-15 12:07:08 +02:00
Christian Khazanovych 5e6320b873 Lastenheft.md aktualisiert 2026-05-15 12:05:17 +02:00
Christian Khazanovych 6e005420e7 Lastenheft 2026-05-15 12:04:02 +02:00
Christian Khazanovych 3c8d3997db Lastenheft 2026-05-15 12:03:31 +02:00
Christian Khazanovych 1b60b47ca3 Project_Charter.md aktualisiert 2026-05-12 22:36:02 +02:00
Christian Khazanovych df89174167 Überarbeitung des Dokuments gemäß Feedback 2026-05-12 22:33:00 +02:00
Christian Khazanovych e48c9c2048 Project_Charter_1.2.pdf gelöscht 2026-05-12 22:32:07 +02:00
Christian Khazanovych 069f6b9f1c Überarbeitung des Dokuments gemäß Feedback 2026-05-12 22:30:42 +02:00
Christian Khazanovych 13603705aa Project_Charter_1.2.pdf gelöscht 2026-05-12 22:30:27 +02:00
Christian Khazanovych da5383dd6e Überarbeitung des Dokuments gemäß Feedback 2026-05-12 22:29:09 +02:00
Christian Khazanovych 6be01feab3 Project_Charter_1.2.pdf gelöscht 2026-05-12 22:28:30 +02:00
Christian Khazanovych 533d97bcba Überarbeitung des Dokuments gemäß Feedback 2026-05-12 17:36:04 +02:00
Christian Khazanovych de64aef18b Project_Charter_1.2.pdf gelöscht 2026-05-12 17:35:52 +02:00
Christian Khazanovych 77732c8a5f Überarbeitung des Dokuments gemäß Feedback 2026-05-12 16:29:16 +02:00
Christian Khazanovych 07a21673c2 Project_Charter_1.2.pdf gelöscht 2026-05-12 16:28:53 +02:00
Christian Khazanovych 5d43abd8aa Project_Charter.md aktualisiert 2026-05-12 16:28:44 +02:00
Christian Khazanovych 0b87e9549e Project_Charter.md aktualisiert 2026-05-12 16:26:22 +02:00
Christian Khazanovych e02f5218da Überarbeitung des Dokuments gemäß Feedback 2026-05-12 16:24:38 +02:00
Christian Khazanovych 53cc4b0d1a Project_Charter_1.2.pdf gelöscht 2026-05-12 16:23:33 +02:00
Christian Khazanovych ee70c747e3 Dateien nach "/" hochladen
Überarbeitung des Dokuments gemäß Feedback
2026-05-12 16:23:20 +02:00
Christian Khazanovych e3fddec533 Project_Charter_Team_3.pdf gelöscht 2026-05-12 16:22:22 +02:00
Christian Khazanovych 4d12875b58 Project_Charter 2026-04-15 15:53:58 +02:00
Christian Khazanovych 2bdaa4094f Project Charter 2026-04-14 17:34:35 +02:00
Christian Khazanovych 596eadaaf1 README.md gelöscht 2026-04-14 17:16:03 +02:00
Christian Khazanovych 266e543317 .gitignore gelöscht 2026-04-13 16:26:46 +02:00
Christian Khazanovych 131c986489 Initial commit 2026-03-25 10:51:35 +01:00
48 changed files with 4945 additions and 104 deletions

BIN
.gitignore vendored

Binary file not shown.

View File

@ -1,4 +0,0 @@
{
"java.project.sourcePaths": ["src/main/java"],
"java.project.outputPath": "out"
}

View File

@ -0,0 +1,250 @@
Software Engineering 1 | Lastenheft | Team 3 |
Frakturierungssystem
Datum: 15.05.2026 | Version: 1.0
Weiterleitung zum Git: <https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3>
Inhalt
[1\. Einleitung und Zielbestimmung 2](#_Toc229738710)
[1.1 Zweck des Dokuments 2](#_Toc229738711)
[1.2 Projektziele 3](#_Toc229738712)
[1.3 Nicht-Ziele 3](#_Toc229738713)
[2\. Systemkontext und Rahmenbedingungen 4](#_Toc229738714)
[2.1 Einsatzkontext 4](#_Toc229738715)
[2.2 Rechtliche und Regulatorische Vorgaben 4](#_Toc229738716)
[3\. Stakeholder und Benutzergruppen 4](#_Toc229738717)
[3.1 Stakeholder 4](#_Toc229738718)
[3.2 Benutzerrollen 5](#_Toc229738719)
[4\. Fachliche Anforderungen (Funktionale Anforderungen) 5](#_Toc229738720)
[5\. Qualitätsanforderungen / Nicht-funktionale Anforderungen 6](#_Toc229738721)
[6\. Daten, Schnittstellen und Geschäftsregeln 7](#_Toc229738722)
[6.1 Datenobjekte (fachliche Sicht) 7](#_Toc229738723)
[6.2 Beziehungen zwischen Datenobjekten 8](#_Toc229738724)
[6.3 Schnittstellen 9](#_Toc229738725)
[6.4 Geschäftsregeln 9](#_Toc229738726)
[7\. Abnahmekriterien 10](#_Toc229738727)
[8\. Glossar 11](#_Toc229738728)
[8.1 Abkürzungsverzeichnis 11](#_Toc229738729)
[8.2 Begriffserklärung 11](#_Toc229738730)
Freigabeübersicht
| Autor | Freigebenden | Prüfer |
| ---------------------- | -------------- | ----------------------- |
| Khazanovych, Christian | Winkler, Louis | Prof. Dr. Marmitt, Gerd |
| Entwickler | Entwickler | Modulverantwortlicher |
| 15.05.2026 | 15.05.2026 | Datum, Unterschrift |
Dokumentenhistorie
| Version | Datum | Autor | Grund der Änderung |
| ------- | ---------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| 1.0 | 12.05.2026 | Christian<br><br>Khazanovych | Initiale Erstellung des Lastenhefts und Kapitel 1-3 sowie die Ergänzung dieser |
| 1.1 | 14.05.2026 | Kutlu Patir, Taha Erdogan, Sarav Guli | Initiale Erstellung von Kapitel 4-5. Sowie alle anderen Gruppen Ergänzung der jeweils 2 Anforderungen |
| 1.2 | 14.05.2026 | Robin Senger | Initiale Erstellung von Kapitel 6 sowie Ergänzung der<br><br>Daten und Schnittstellen |
| 1.3 | 14.05.2026 | Louis Winkler | Ergänzung der Geschäftsregeln (Kapitel 6) |
| 1.4 | 15.05.2026 | Meltem Bardakci | Initiale Erstellung sowie Ergänzung von Kapitel 7-8 |
# Einleitung und Zielbestimmung
## Zweck des Dokuments
Dieses Lastenheft spezifiziert die fachlichen Anforderungen an eine lokale Fakturierungsanwendung für Kleinstunternehmen und Freiberufler. Es beschreibt aus Sicht des Auftraggebers, welche Funktionalitäten das System bereitstellen muss und dient als verbindliche Basis für:
- Die technische Konzeption (Pflichtenheft) und die Systemarchitektur
- Die Planung und Durchführung von Akzeptanztests im Rahmen der Qualitätssicherung
- Die rechtssichere Umsetzung regulatorischer Anforderungen
## Projektziele
Die primären Projektziele sind nicht isoliert zu betrachten, sondern bilden eine integrierte Wertschöpfungskette für den Endanwender. Das Bestandsmanagement (Artikel- und Preisstrukturen) und die Kundenstammdatenverwaltung bilden das datentechnische Fundament der Anwendung. Diese beiden Kernkomponenten führen im Belegworkflow prozessual zusammen, indem dort die Stammdaten effizient und konsistent zu Angeboten und rechtskonformen Rechnungen verarbeitet werden. Überspannt und zusammengehalten werden diese funktionalen Säulen von einem User Interface (UI), welches durch eine intuitive Nutzerführung sicherstellt, dass die Datenpflege und der Belegprozess ohne hohe Einarbeitungszeit und fehlerfrei vom Einzelanwender bedient werden können.
| Ziele | Begründung |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Bestandsmanagement | Das System muss eine persistente Speicherung und Pflege von Artikelstammdaten, Preisen und Lagerbeständen ermöglichen |
| Kundenstammdaten | Bereitstellung einer zentralen Datenbank zur Verwaltung von Kundeninformationen inklusive einer lückenlosen Transaktionshistorie |
| User Interface (UI) | Realisierung einer intuitiven und ergonomischen Benutzeroberfläche zur Minimierung der Einarbeitungszeit für Endanwender |
| Belegworkflow | Abbildung des vollständigen kaufmännischen Prozesses durch die systemgestützten Überführung von Angeboten in rechtskonforme Rechnungen |
## Nicht-Ziele
Um den Fokus auf die Kernfunktionalitäten zu wahren, werden folgende Aspekte ausdrücklich, als nicht Bestandteil des Projekts definiert:
- Cloud & Konnektivität: Es erfolgt keine Implementierung von Cloud Schnittstellen oder externen Datenbank Backends. Das System operiert ausschließlich im Offline Modus.
- Single-User-Limitierung: Eine Unterstützung für gleichzeitige Zugriffe im Netzwerk oder eine differenzierte Benutzerverwaltung (Rollen/Rechte) ist nicht vorgesehen.
- Support: Es besteht kein Anspruch auf kommerzielle Wartung, Fehlerbehebung nach Projektabschluss oder vertraglich zugesicherte Garantien.
- Buchhalterische Tiefe: Das System dien der Belegerstellung, nicht der Buchführung. Steuerrechtliche Berechnungen, Bilanzen oder eine Anbindung an das Finanzamt sich nicht Teil des Funktionsumfangs.
- Mobile & Web: Die Anwendung wird rein als Desktop Lösung entwickelt. Mobile Endgeräte oder Web-Browser werden nicht unterstützt.
# Systemkontext und Rahmenbedingungen
## Einsatzkontext
Die Anwendung ist als unabhängiges Desktop System konzipiert, das ohne permanente Internetverbindung operiert. Der Fokus liegt auf der lokalen Verarbeitung am Einzelarbeitsplatz eines Freiberufler- oder Kleinstunternehmen.
Schnittstellen und Datenfluss:
- Eingabe: Manuelle Erfassung von Stammdaten (Produkte, Preise, Kunden) sowie Transaktionsdaten durch den Anwender.
- Speicherung: Die Datenhaltung erfolgt persistent auf dem lokalen Dateisystem des Nutzers, um Datenschutzvorgaben (DSGVO) durch physische Datenhoheit zu gewährleisten.
- Ausgabe: Generierung kaufmännischer Dokumente (Angebote, Rechnungen), die für den Export oder Druck bereitgestellt werden.
## Rechtliche und Regulatorische Vorgaben
Die Software ist darauf ausgelegt, die administrativen Hürden der Rechnungslegung zu minimieren und gleichzeitig Rechtssicherheit zu bieten.
- Steuerrecht: Jede erzeugte Rechnung integriert zwingend alle Pflichtangaben gemäß § 14 UStG, darunter fortlaufende Nummern, Steuernummern und korrekte Leistungszeiträume.
- Revisionssicherheit (GoBD): Das System implementiert einen Schreibschutz für finale Belege, um nachträgliche Manipulationen auszuschließen und die Integrität der Buchführung zu wahren.
- Datenschutz: Durch das Prinzip der Datensparsamkeit und die rein lokale Infrastruktur wird eine unbefugte Übermittlung von Kundendaten an Dritte systemisch verhindert.
# Stakeholder und Benutzergruppen
## Stakeholder
| ID | Stakeholder | Beschreibung |
| ----- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SH-01 | Auftraggeber | Der Modulverantwortliche, der die fachlichen Anforderungen definieren, die Einhaltung der gesetzlichen Rahmenbedingungen (z. B. GoBD) fordern und das Projektergebnis anhand der Abnahmekriterien bewerten |
| SH-02 | Projektteam | Erfolgreiche Umsetzung des Projekts und der Ziele |
| SH-03 | Endnutzer | Funktionierender, lokal nutzbarer Fakturierungsablauf |
## Benutzerrollen
Da das System explizit für den Einzelplatzbetrieb konzipiert ist, wird im operativen Kontext auf eine komplexe Hierarchie verzichtet. Stattdessen fokussiert sich die Anwendung auf eine zentrale Rolle, die den gesamten kaufmännischen Workflow abbildet
Rolle: Einzelanwender:in
Diese Rolle repräsentiert die natürliche Person (z.B. Freiberufler:in oder Inhaber:in eines Kleinstunternehmens), die das System vollumfänglich nutzt. Die Konzentration auf eine einzige Rolle stellt sicher, dass alle kaufmännischen Prozesse ohne Medienbruch in einer Hand bleiben.
# Fachliche Anforderungen (Funktionale Anforderungen)
Die funktionalen Anforderungen beschreiben, welche fachlichen Funktionen der Einzelanwender mit dem System ausführen können muss. Jede Anforderung ist testbar formuliert und enthält ein konkretes Erfüllungskriterium.
| ID | Priorität | Anforderung | Anforderung gilt als erfüllt |
| ----- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| BA-01 | Muss | Das System muss dem Einzelanwender ermöglichen, neue Kundendatensätze mit Name, Anschrift und Kontaktinformationen anzulegen, um Kunden für spätere Angebote und Rechnungen verwenden zu können. | Nach Eingabe und Speicherung der Kundendaten wird der Kunde dauerhaft in der Kundenübersicht angezeigt und kann für ein Dokument ausgewählt werden. |
| BA-02 | Muss | Das System muss dem Einzelanwender ermöglichen, Produktdaten mit Bezeichnung, Preis und Bestand zu speichern, um Produkte in kaufmännischen Dokumenten verwenden zu können. | Nach dem Speichern eines Produkts erscheint dieses in der Produktübersicht und kann bei der Erstellung eines Angebots oder einer Rechnung ausgewählt werden. |
| BA-03 | Muss | Das System muss dem Einzelanwender ermöglichen, ein Angebot für einen vorhandenen Kunden mit mindestens einer Produktposition zu erstellen, um einen Geschäftsvorgang vor der Rechnungsstellung zu dokumentieren. | Nach Auswahl eines Kunden und mindestens eines Produkts erstellt das System ein Angebot mit Positionsübersicht, Nettobetrag, Steuerbetrag und Gesamtbetrag. |
| BA-04 | Muss | Das System muss dem Einzelanwender ermöglichen, aus einem vorhandenen Angebot eine Rechnung zu erzeugen, um den Dokumentenprozess von Angebot bis Rechnung abzubilden. | Nach Auswahl eines vorhandenen Angebots erzeugt das System eine Rechnung, übernimmt Kundendaten und Positionen und vergibt eine eindeutige Rechnungsnummer. |
# Qualitätsanforderungen / Nicht-funktionale Anforderungen
Die Qualitätsanforderungen beschreiben, unter welchen messbaren Bedingungen das System die fachlichen Funktionen erfüllen soll.
| ID | Priorität | Anforderung | Anforderung gilt als erfüllt |
| ---- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Q-01 | Muss | Benutzerfreundlichkeit Das System muss so intuitiv gestaltet sein, dass ein neuer Einzelanwender ohne technische Vorkenntnisse oder Schulung die Kernprozesse (Kunde anlegen, Produkt erfassen, Rechnung erstellen) bedienen kann. | Die dreiteilige Aufgabenkette kann in einem manuellen Usability-Test ohne Hilfestellung innerhalb von maximal 10 Minuten erfolgreich durchgeführt werden. |
| Q-02 | Muss | Datensicherheit & Zuverlässigkeit (Data Integrity): Das System muss sicherstellen, dass alle gespeicherten Kunden-, Produkte und Dokumentendaten dauerhaft vollständig erhalten bleiben und vor unbefugtem Zugriff geschützt sind. | Nach dem Schließen und erneutem Öffnen der Anwendung stehen alle zuvor gespeicherten Daten unverändert zur Verfügung. Nicht berechtigte Personen können nicht auf die gespeicherte Daten zugreifen. |
| Q-03 | Muss | Revisionssicherheit (GoBD-Konformität): Das System muss final erzeugte Rechnungen systemseitig vor nachträglicher Manipulation oder direktem Überschreiben schützen. | Nach dem finalen Erzeugen einer Rechnung können Rechnungsnummer, Kundendaten, Positionen und Beträge nicht mehr über die GUI verändert oder gelöscht werden. |
# Daten, Schnittstellen und Geschäftsregeln
## Datenobjekte (fachliche Sicht)
Die folgenden Datenobjekte bilden den fachlichen Kern des Systems. Eine detaillierte UML-Darstellung (Klassendiagramm, ER-Diagramm) erfolgt im Pflichtenheft.
| Objekt | Kernattribute |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Kunde | Kunden-ID (eindeutig, fortlaufend), Name/Firmenname, vollständige Anschrift, Steuernummer/USt-IdNr., E-Mail, Telefon |
| Produkt | Produkt-ID (eindeutig, fortlaufend), Bezeichnung, Netto-Einzelpreis, Mehrwertsteuersatz, optionale Beschreibung |
| Dokumentposition | Produktreferenz, Menge, Einzelpreis (Snapshot zum Erstellungszeitpunkt), Steuersatz (Snapshot), Positionssumme |
| Angebot | Angebotsnummer, Erstellungsdatum, Gültigkeitsdatum, Kundenreferenz, Positionen, Netto-/Steuer-/Bruttosumme, Status (offen / überführt / verworfen) |
| Auftragsbestätigung | AB-Nummer, Datum, Kundenreferenz, Referenz auf Angebot, Positionen, Netto-/Steuer-/Bruttosumme, Status |
| Lieferschein | Lieferscheinnummer, Lieferdatum, Kundenreferenz, Referenz auf Auftragsbestätigung, Positionen (Bezeichnung, Menge - keine Preise) |
| Rechnung | Rechnungsnummer (fortlaufend, lückenlos), Rechnungsdatum, Leistungsdatum, Kundenreferenz, Referenz auf Lieferschein, Positionen, Netto-/Steuer-/Bruttosumme, Zahlungsziel, Status (offen / bezahlt), alle Pflichtangaben gem. § 14 UStG |
## Beziehungen zwischen Datenobjekten
| Beziehung | Multiplizität |
| ---------------------------------- | ------------- |
| Kunde → Angebot | 1 : n |
| Angebot → Auftragsbestätigung | 1 : 0..1 |
| Auftragsbestätigung → Lieferschein | 1 : 0..1 |
| Lieferschein → Rechnung | 1 : 0..1 |
| Produkt → Dokumentposition | 1 : n |
## Schnittstellen
| ID | Schnittstelle | Zweck |
| ----- | --------------------------- | ------------------------------------------------------------------------------------ |
| IF-01 | Benutzerschnittstelle (GUI) | Interaktive Oberfläche zur Bedienung aller Module durch den Einzelanwender |
| IF-02 | Lokales Dateisystem | Persistente Speicherung aller Stamm- und Bewegungsdaten sowie exportierter Dokumente |
| IF-03 | Druck-/Export-Schnittstelle | Erzeugung druckbarer PDF-Dokumente (Angebot, Lieferschein, Rechnung) |
Eine Anbindung an externe Dienste, Cloud-Speicher oder Buchhaltungssysteme ist ausdrücklich nicht Bestandteil des Systems (vgl. Abschnitt 1.3 Nicht-Ziele).
## Geschäftsregeln
| ID | Geschäftsregel |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| GR-01 | Fortlaufende Rechnungsnummern: Jede neue Rechnung erhält automatisch die nächste freie, lückenlose Rechnungsnummer. Bereits vergebene Nummern dürfen nicht wiederverwendet werden (GoBD-konform). |
| GR-02 | Unveränderlichkeit finaler Belege: Sobald ein Dokument den Status „versendet" bzw. „festgeschrieben" erreicht, sind inhaltliche Änderungen systemseitig gesperrt. Korrekturen erfolgen ausschließlich über neue Storno- oder Korrekturdokumente. |
| GR-03 | Snapshot-Prinzip (Preisübernahme): Beim Hinzufügen einer Produktposition zu einem Dokument werden Einzelpreis und Steuersatz zum Zeitpunkt der Erstellung unveränderlich im Dokument gespeichert. Spätere Preisänderungen am Produkt wirken sich nicht auf bestehende Dokumente aus. |
| GR-04 | Referenzielle Integrität: Kunden- und Produktdatensätze dürfen nicht gelöscht werden, solange sie in einem aktiven oder archivierten Dokument referenziert sind. Das System verweigert den Löschvorgang und gibt einen entsprechenden Hinweis aus. |
| GR-05 | Dokumentenketten-Konsistenz: Ein Folgedokument (z. B. Auftragsbestätigung aus Angebot) übernimmt automatisch Kunde, Positionen und Mengen aus dem Vorgängerdokument und speichert eine eindeutige Rückreferenz. Das Vorgängerdokument wechselt dabei in den Status „überführt". |
| GR-06 | Summenberechnung: Nettosumme = Summe (Menge × Einzelpreis); USt.-Betrag = Nettosumme × MwSt.-Satz; Bruttosumme = Nettosumme + USt.-Betrag. Die Berechnung erfolgt automatisch beim Speichern eines Dokuments. |
# Abnahmekriterien
| ID | Bezug (Anforderung) | Abnahmekriterium | Testmethode |
| ----- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| AK-01 | BA-01 | Ein neuer Kunde kann nur gespeichert werden, wenn alle Pflichtfelder gemäß § 14 UStG ausgefüllt sind. | Manueller Test (Eingabeprüfung) |
| AK-02 | GR-04 / BA-01 | Das Löschen eines Kunden, der bereits in einer Rechnung referenziert wird, wird systemseitig mit einer Fehlermeldung verhindert. | Negativtest (Löschversuch) |
| AK-03 | Q-03 | Finalisierte Belege (Rechnungen) erhalten einen Schreibschutz und können nicht mehr editiert werden. | Funktionsprüfung |
| AK-04 | Q-02 | Die Anwendung startet und funktioniert ohne vorhandene Internetverbindung; Daten werden lokal gespeichert. | Systemtest (ohne Netzwerk) |
| AK-05 | BA-04 | Das System generiert aus einem bestehenden Angebot eine rechtskonforme Rechnung unter Beibehaltung der Daten. | Workflow-Test |
# Glossar
## Abkürzungsverzeichnis
| Abkürzung | Bedeutung |
| --------- | -------------------------------------------------------------------------------------------------------------------------- |
| SH | Stakeholder |
| BA | Funktionale Benutzeranforderungen |
| Q | Qualitätsanforderungen |
| IF | Interface |
| AK | Abnahmekriterien |
| AB-Nummer | Auftragsbestätigung |
| n/m | Kardinalitäten/Multiplizitäten in den Datenbeziehungen (z.B. 1 : n) |
| GoBD | Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern, Aufzeichnungen und Unterlagen in elektronischer Form. |
| UI | User Interface; die grafische Benutzeroberfläche, über die der Anwender mit dem System interagiert. |
| GUI | Graphical User Interface (Grafische Benutzeroberfläche) |
| UStG | Umsatzsteuergesetz; insbesondere § 14 regelt die Pflichtangaben auf einer Rechnung. |
| DSGVO | Datenschutz-Grundverordnung der Europäischen Union; regelt die Verarbeitung personenbezogener Daten. |
## Begriffserklärung
| Begriff | Erklärung |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fakturierung | Der Prozess der Rechnungsstellung für erbrachte Leistungen oder gelieferte Waren. |
| Medienbruch | Ein Informationsverlust oder Mehraufwand, der entsteht, wenn Daten zwischen verschiedenen Systemen manuell übertragen werden müssen. |
| Revisionssicherheit | Die Eigenschaft eines Systems, Daten so zu archivieren, dass sie nachträglich nicht mehr unbemerkt verändert oder gelöscht werden können. |
| Stammdaten | Grundlegende Informationen über Kunden oder Produkte, die über einen längeren Zeitraum unverändert bleiben. |
| Stakeholder | Personen oder Gruppen, die ein berechtigtes Interesse am Verlauf oder Ergebnis eines Projekts haben. |
| Persistente Speicherung | Die dauerhafte Speicherung von Daten auf einem Datenträger (hier das lokale Dateisystem), sodass diese auch nach dem Beenden der Anwendung erhalten bleibt. |
| Snapshot-Prinzip | Ein Verfahren, bei dem Daten (wie Preise oder Steuersätze) zum Zeitpunkt einer Transaktion fest im Dokument eingefroren werden, damit spätere Änderungen an den Stammdaten das historische Dokument nicht verfälschen. |
| Pflichtenheft | Die technische Antwort des Entwicklers auf das Lastenheft; es beschreibt die detaillierte Umsetzung (wie z. B. die UML-Darstellung). |
| Bewegungsdaten | Daten, die im Gegensatz zu Stammdaten durch Geschäftsvorgänge entstehen und sich ständig ändern (z. B. Angebote, Rechnungen). |
| GoBD-Konformität | Die Einhaltung der Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern in elektronischer Form, insbesondere zum Schutz gegen Manipulation. |

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,713 @@
**Pflichtenheft Benutzeroberfläche Fakturierungssystem**
Software Engineering 1 Pflichtenheft zum Lastenheft Team 3 Modul: Benutzeroberfläche Version: 1.0 Datum: 12.06.2026
**Inhalt**
- Metainformationen
- Einleitung und Bezug zum Lastenheft
- Systemübersicht
- Funktionale Anforderungen
- Nichtfunktionale Anforderungen
- Daten und Schnittstellen
- Systemarchitektur
- Abnahmekriterien
- Traceability Matrix
- Modultestplan
- Glossar
**1\. Metainformationen**
**1.1 Dokumentinformationen**
| **Feld** | **Inhalt** |
| ------------ | ---------------------------------------------------- |
| Dokumentname | Pflichtenheft Benutzeroberfläche Fakturierungssystem |
| Version | 1.0 |
| Datum | 12.06.2026 |
| Projekt | Lokales Fakturierungssystem |
| Modul | Benutzeroberfläche |
| Bezug | Lastenheft Team 3, Version 1.0 |
| Gruppe | Gruppe I |
| Autorin | Meltem Bardakci |
**1.2 Freigabeübersicht**
| **Rolle** | **Name** | **Datum** |
| --------- | ----------------- | ---------- |
| Autorin | Meltem Bardakci | 12.06.2026 |
| Prüfer | Prof. Dr. Marmitt | |
**1.3 Änderungshistorie**
| **Version** | **Datum** | **Autorin** | **Änderung** |
| ----------- | ---------- | --------------- | ----------------------------------------------------------------------- |
| 1.0 | 12.06.2026 | Meltem Bardakci | Initiale Erstellung des Pflichtenhefts für das Modul Benutzeroberfläche |
**2\. Einleitung und Bezug zum Lastenheft**
**2.1 Zweck des Dokuments**
Dieses Pflichtenheft beschreibt die technische Umsetzung der Benutzeroberfläche des Fakturierungssystems. Es basiert auf dem Lastenheft von Team 3 und konkretisiert die dort beschriebenen Anforderungen aus Sicht der Umsetzung.
Der Schwerpunkt dieses Pflichtenhefts liegt auf der Bedienung durch den Einzelanwender. Dazu gehören Navigation, Eingabemasken, Übersichtstabellen, Validierung, Fehlermeldungen, Statusanzeigen und die einfache Durchführung der Kernprozesse.
**2.2 Ziel des UI Moduls**
Ziel des UI Moduls ist eine übersichtliche und verständliche Desktop Oberfläche. Der Einzelanwender soll die wichtigsten Funktionen des Fakturierungssystems ohne lange Einarbeitung bedienen können.
Die Oberfläche soll folgende Aufgaben unterstützen:
- Kunden erfassen und anzeigen
- Produkte erfassen und anzeigen
- Angebote erstellen
- Auftragsbestätigungen und Lieferscheine anzeigen oder erzeugen
- Rechnungen aus vorhandenen Angeboten erzeugen
- Pflichtfelder prüfen
- Fehlermeldungen verständlich anzeigen
- finalisierte Rechnungen als schreibgeschützt kennzeichnen
- PDF Export über eine klare Bedienaktion starten
**2.3 Abgrenzung**
Dieses Pflichtenheft beschreibt nur das Modul Benutzeroberfläche. Die interne Speicherung, die vollständige Beleglogik und die steuerrechtliche Detailprüfung werden nur so weit beschrieben, wie sie für die Bedienoberfläche sichtbar sind.
Nicht Bestandteil dieses Moduls sind:
- externe Cloud Anbindung
- Mehrbenutzerverwaltung
- mobile Oberfläche
- Web Anwendung
- vollständige Buchhaltung
- Schnittstellen zu Finanzamt oder Buchhaltungssystemen
**2.4 Referenzen**
- Lastenheft Fakturierungssystem, Team 3, Version 1.0
- Vorlesungsunterlagen Software Engineering 1, Thema Lastenheft und Pflichtenheft
- Java Dokumentation zu Datentypen String, int, BigDecimal und List
- UML Grundlagen für Klassen und Sequenzdiagramme
**2.5 Stakeholder und Kontext**
Maßgeblicher Akteur ist der Einzelanwender bzw. die Einzelanwenderin des Fakturierungssystems. Diese Person nutzt die Benutzeroberfläche, um Kunden, Produkte und Dokumente aufzurufen, Daten einzugeben, Angebote zu erstellen, Rechnungen aus vorhandenen Angeboten zu erzeugen und PDF Dateien zu exportieren.
Angrenzende Komponenten sind die Kundenverwaltung, die Produktverwaltung, der DocumentService, der ValidationService, der MessageService und der PdfExportService. Die Benutzeroberfläche ruft diese Komponenten über definierte Schnittstellen auf und stellt deren Ergebnisse sichtbar dar.
Nicht Aufgabe der Benutzeroberfläche ist die dauerhafte Speicherung oder fachliche Berechnung der Belegdaten. Diese Aufgaben liegen bei den fachlichen Services.
**3\. Systemübersicht**
Das Fakturierungssystem wird als lokale Desktop Anwendung umgesetzt. Der Einzelanwender arbeitet über eine grafische Benutzeroberfläche mit dem System.
Die Benutzeroberfläche ist die zentrale Schnittstelle zwischen Anwender und Systemfunktionen. Über die Oberfläche kann der Anwender Kunden, Produkte und Dokumente verwalten. Außerdem erhält er Rückmeldungen zu erfolgreichen Aktionen, fehlerhaften Eingaben und dem Status von Dokumenten.
Das UI Modul besteht aus folgenden Bereichen:
| **Bereich** | **Aufgabe** |
| -------------------- | ------------------------------------------------------------------------------------------ |
| Hauptfenster | Einstiegspunkt der Anwendung |
| Navigation | Wechsel zwischen Kunden, Produkten und Dokumenten |
| Kundenmaske | Eingabe und Anzeige von Kundendaten |
| Produktmaske | Eingabe und Anzeige von Produktdaten |
| Dokumentenmaske | Erstellung und Anzeige von Angeboten, Auftragsbestätigungen, Lieferscheinen und Rechnungen |
| Validierungsanzeige | Anzeige fehlender oder ungültiger Eingaben |
| Statusanzeige | Anzeige von Erfolgsmeldungen, Warnungen und Fehlern |
| Exportdialog | Auswahl des Speicherorts für PDF Dateien |
| Schreibschutzanzeige | Kennzeichnung finalisierter Rechnungen |
Die Oberfläche verarbeitet selbst keine dauerhafte Speicherung. Sie nimmt Eingaben entgegen, zeigt Daten an und übergibt Benutzeraktionen an die zuständigen Systemkomponenten.
**4\. Funktionale Anforderungen**
**PH-UI-01 Hauptfenster und Navigation**
Das System stellt ein Hauptfenster bereit, das nach dem Start der Anwendung angezeigt wird. Im Hauptfenster befindet sich eine Navigation zu den wichtigsten Bereichen.
Die Navigation enthält mindestens folgende Menüpunkte:
| **Menüpunkt** | **Ziel** |
| ------------- | --------------------------------------------------------------------------- |
| Kunden | Öffnet die Kundenübersicht |
| Produkte | Öffnet die Produktübersicht |
| Dokumente | Öffnet die Dokumentenübersicht |
| Export | Öffnet Exportfunktionen oder startet den PDF Export bei geöffnetem Dokument |
Die Navigation muss dauerhaft sichtbar oder schnell erreichbar sein. Der Anwender soll jederzeit erkennen können, in welchem Bereich er sich befindet.
Erfüllungskriterium: Nach dem Start der Anwendung ist das Hauptfenster sichtbar und der Anwender kann zwischen Kunden, Produkten und Dokumenten wechseln.
**PH-UI-02 Kundenmaske**
Das System stellt eine Kundenmaske bereit. In dieser Maske kann der Einzelanwender Kundendaten erfassen, anzeigen und bearbeiten.
Die Kundenmaske enthält folgende Eingabefelder:
| **Feld** | **Datentyp** | **Pflichtfeld** |
| -------------------------- | ------------ | --------------- |
| Name oder Firmenname | String | Ja |
| Anschrift | String | Ja |
| Steuernummer oder USt IdNr | String | Nein |
| E-Mail | String | Nein |
| Telefon | String | Nein |
Die Kundenmaske enthält folgende Schaltflächen:
| **Schaltfläche** | **Funktion** |
| ---------------- | -------------------------------------------------- |
| Speichern | Speichert den Kundendatensatz |
| Abbrechen | Verwirft die Eingabe |
| Bearbeiten | Aktiviert die Bearbeitung eines bestehenden Kunden |
| Zurück | Führt zurück zur Kundenübersicht |
Erfüllungskriterium: Der Anwender kann Kundendaten über die Maske eingeben und speichern. Fehlende Pflichtfelder werden sichtbar markiert.
**PH-UI-03 Produktmaske**
Das System stellt eine Produktmaske bereit. In dieser Maske kann der Einzelanwender Produktdaten erfassen, anzeigen und bearbeiten.
Die Produktmaske enthält folgende Eingabefelder:
| **Feld** | **Datentyp** | **Pflichtfeld** |
| ------------------ | ------------ | --------------- |
| Bezeichnung | String | Ja |
| Netto Einzelpreis | BigDecimal | Ja |
| Mehrwertsteuersatz | BigDecimal | Ja |
| Beschreibung | String | Nein |
| Bestand | int | Ja |
Die Produktmaske prüft, ob der Preis größer oder gleich 0 ist. Der Bestand darf nicht negativ sein.
Erfüllungskriterium: Der Anwender kann ein Produkt über die Maske anlegen. Ungültige Zahlenwerte werden abgelehnt und mit einer verständlichen Fehlermeldung angezeigt.
**PH-UI-04 Dokumentenübersicht**
Das System stellt eine Dokumentenübersicht bereit. Dort kann der Anwender vorhandene Dokumente einsehen.
Die Dokumentenübersicht zeigt mindestens folgende Informationen:
| **Spalte** | **Beschreibung** |
| -------------- | --------------------------------------------------------- |
| Dokumentnummer | Nummer des Dokuments |
| Dokumenttyp | Angebot, Auftragsbestätigung, Lieferschein oder Rechnung |
| Kunde | Zugeordneter Kunde |
| Datum | Erstellungsdatum |
| Status | offen, überführt, festgeschrieben, bezahlt oder verworfen |
| Bruttosumme | Gesamtbetrag, sofern für den Dokumenttyp relevant |
Der Anwender kann Dokumente auswählen und öffnen.
Erfüllungskriterium: Vorhandene Dokumente werden tabellarisch angezeigt und können geöffnet werden.
**PH-UI-05 Dokumentenmaske zur Erstellung von Angeboten**
Das System stellt eine Dokumentenmaske zur Erstellung von Angeboten bereit. Der Anwender wählt einen vorhandenen Kunden und mindestens eine Produktposition aus.
Die Maske enthält folgende Bereiche:
| **Bereich** | **Inhalt** |
| ---------------- | ------------------------------------ |
| Kundenauswahl | Auswahl eines vorhandenen Kunden |
| Positionsbereich | Hinzufügen von Produkten mit Menge |
| Summenbereich | Anzeige von Netto, Steuer und Brutto |
| Aktionsbereich | Speichern, Abbrechen und PDF Export |
Die Oberfläche verhindert, dass ein Angebot ohne Kunde oder ohne Produktposition gespeichert wird.
Erfüllungskriterium: Der Anwender kann ein Angebot über die Oberfläche erstellen. Die Summen werden sichtbar angezeigt.
**PH-UI-06 Rechnung aus Angebot erzeugen**
Das System stellt in der Oberfläche eine Aktion bereit, mit der der Anwender aus einem vorhandenen Angebot eine Rechnung erzeugen kann.
Der Workflow umfasst:
| **Ausgangsdokument** | **Folgedokument** |
| -------------------- | ----------------- |
| Angebot | Rechnung |
Die Oberfläche zeigt die Aktion zur Rechnungserstellung nur an, wenn ein passendes Angebot ausgewählt wurde. Die Benutzeroberfläche erzeugt die Rechnung nicht selbst, sondern ruft dafür die Methode createInvoiceFromOffer(String offerId) des DocumentService auf.
Erfüllungskriterium: Der Anwender kann über eine sichtbare Schaltfläche aus einem vorhandenen Angebot eine Rechnung erzeugen.
**PH-UI-07 Eingabevalidierung**
Das System prüft Eingaben spätestens beim Speichern. Fehlerhafte Eingaben werden sichtbar angezeigt.
Folgende Validierungen werden über die Oberfläche sichtbar gemacht:
| **Bereich** | **Prüfung** |
| ----------- | ------------------------------------------------------------- |
| Kunde | Name und Anschrift dürfen nicht leer sein |
| Produkt | Bezeichnung, Preis, Steuersatz und Bestand müssen gültig sein |
| Dokument | Kunde und mindestens eine Position müssen vorhanden sein |
| Position | Menge muss größer als 0 sein |
| Rechnung | Finalisierte Rechnung darf nicht bearbeitet werden |
Fehlerhafte Felder werden markiert. Zusätzlich wird eine kurze Fehlermeldung angezeigt.
Erfüllungskriterium: Ungültige Eingaben werden nicht gespeichert und der Anwender erhält eine verständliche Rückmeldung.
**PH-UI-08 Fehlermeldungen und Erfolgsmeldungen**
Das System zeigt klare Rückmeldungen an.
Beispiele für Erfolgsmeldungen:
| **Situation** | **Meldung** |
| ------------------- | ------------------------------------- |
| Kunde gespeichert | Kunde wurde erfolgreich gespeichert |
| Produkt gespeichert | Produkt wurde erfolgreich gespeichert |
| Angebot erstellt | Angebot wurde erfolgreich erstellt |
| Rechnung erzeugt | Rechnung wurde erfolgreich erzeugt |
Beispiele für Fehlermeldungen:
| **Situation** | **Meldung** |
| -------------------- | --------------------------------------------------------------- |
| Pflichtfeld leer | Bitte füllen Sie alle Pflichtfelder aus |
| Preis negativ | Der Preis darf nicht negativ sein |
| Menge ungültig | Die Menge muss größer als 0 sein |
| Rechnung finalisiert | Diese Rechnung ist finalisiert und kann nicht bearbeitet werden |
Erfüllungskriterium: Jede erfolgreiche oder fehlerhafte Aktion erzeugt eine verständliche Rückmeldung.
**PH-UI-09 Schreibschutzanzeige bei finalisierten Rechnungen**
Finalisierte Rechnungen werden in der Oberfläche schreibgeschützt angezeigt. Eingabefelder und Bearbeitungsfunktionen sind deaktiviert.
Die Oberfläche zeigt zusätzlich einen sichtbaren Hinweis an:
| **Status** | **Anzeige** |
| ----------- | --------------------------------------------------- |
| Finalisiert | Diese Rechnung ist finalisiert und schreibgeschützt |
Folgende Daten dürfen bei finalisierten Rechnungen nicht mehr bearbeitet werden:
- Rechnungsnummer
- Kundendaten
- Positionen
- Nettosumme
- Steuerbetrag
- Bruttosumme
- Leistungsdatum
Erfüllungskriterium: Eine finalisierte Rechnung kann über die Oberfläche nicht mehr verändert werden.
**PH-UI-10 PDF Export Bedienung**
Das System stellt eine sichtbare Exportfunktion bereit. Der Anwender kann aus einem geöffneten Dokument eine PDF Datei erzeugen.
Die Oberfläche zeigt einen Exportbutton an. Nach dem Klick öffnet sich ein Dialog zur Auswahl des Speicherorts.
Erfüllungskriterium: Der Anwender kann den PDF Export über die Oberfläche starten und einen lokalen Speicherort auswählen.
**5\. Nichtfunktionale Anforderungen**
**PH-Q-01 Benutzerfreundlichkeit**
Die Oberfläche muss so gestaltet sein, dass ein neuer Einzelanwender die wichtigsten Kernprozesse ohne technische Vorkenntnisse durchführen kann.
Die Kernprozesse sind:
- Kunde anlegen
- Produkt anlegen
- Angebot erstellen
- Rechnung aus vorhandenem Angebot erzeugen
Erfüllungskriterium: Ein neuer Anwender kann diese Prozesse in einem Usability Test ohne Hilfestellung innerhalb von maximal 10 Minuten durchführen.
**PH-Q-02 Übersichtlichkeit**
Die Oberfläche muss klar strukturiert sein. Zusammengehörige Informationen werden gruppiert dargestellt.
| **Bereich** | **Darstellung** |
| ------------- | -------------------------------------------------- |
| Kundendaten | In einer eigenen Kundenmaske |
| Produktdaten | In einer eigenen Produktmaske |
| Dokumentdaten | In einer eigenen Dokumentenmaske |
| Summen | In einem sichtbaren Summenbereich |
| Fehler | In unmittelbarer Nähe zur Eingabe oder als Meldung |
Erfüllungskriterium: Der Anwender erkennt ohne Suche, in welchem Bereich er sich befindet und welche Aktion möglich ist.
**PH-Q-03 Konsistenz**
Schaltflächen, Begriffe und Statusanzeigen werden in der gesamten Oberfläche einheitlich verwendet.
| **Begriff** | **Verwendung** |
| ----------- | ---------------------------------- |
| Speichern | Daten dauerhaft sichern |
| Abbrechen | Eingabe ohne Speicherung verlassen |
| Bearbeiten | Bestehende Daten ändern |
| Exportieren | PDF Datei erzeugen |
| Finalisiert | Dokument ist schreibgeschützt |
Erfüllungskriterium: Gleiche Aktionen werden in allen Masken gleich benannt.
**PH-Q-04 Fehlertoleranz**
Das System soll Fehleingaben verhindern oder frühzeitig erkennen. Fehlerhafte Eingaben dürfen nicht stillschweigend gespeichert werden.
Erfüllungskriterium: Bei jeder fehlerhaften Eingabe wird das Speichern verhindert und eine verständliche Fehlermeldung angezeigt.
**PH-Q-05 Lokale Nutzbarkeit**
Die Oberfläche muss ohne Internetverbindung nutzbar sein. Alle sichtbaren Funktionen des UI Moduls müssen lokal bedienbar sein.
Erfüllungskriterium: Die Anwendung startet und die Oberfläche ist ohne Internetverbindung nutzbar.
**PH-Q-06 Lesbarkeit**
Texte, Tabellen, Eingabefelder und Schaltflächen müssen gut lesbar sein. Pflichtfelder sollen eindeutig erkennbar sein.
Erfüllungskriterium: Pflichtfelder sind markiert und alle Beschriftungen sind verständlich formuliert.
**6\. Daten und Schnittstellen**
**6.1 Datenobjekte in der Benutzeroberfläche**
Die Benutzeroberfläche zeigt Daten aus den fachlichen Modulen an. Das UI Modul berechnet und speichert diese Daten nicht selbst dauerhaft. Es nimmt Eingaben entgegen, zeigt Daten an und übergibt Benutzeraktionen an die zuständigen Systemkomponenten.
| **Objekt** | **Sichtbare Daten in der Oberfläche** |
| ------------------- | ------------------------------------------------------------------------------------------------ |
| Kunde | Name, Anschrift, Steuernummer, E Mail, Telefon |
| Produkt | Bezeichnung, Netto Einzelpreis, Mehrwertsteuersatz, Beschreibung, Bestand |
| Dokumentposition | Produkt, Menge, Einzelpreis, Steuersatz, Positionssumme |
| Angebot | Angebotsnummer, Kunde, Datum, Positionen, Summen, Status |
| Auftragsbestätigung | AB Nummer, Kunde, Datum, Positionen, Status |
| Lieferschein | Lieferscheinnummer, Kunde, Lieferdatum, Positionen, Status |
| Rechnung | Rechnungsnummer, Kunde, Rechnungsdatum, Leistungsdatum, Positionen, Summen, Zahlungsziel, Status |
**6.2 UI interne Datenmodelle**
Für die Oberfläche werden interne UI Modelle verwendet. Diese Modelle dienen dazu, Eingaben aus Formularen zwischenzuspeichern und für die Validierung vorzubereiten.
| **UI Modell** | **Zweck** |
| ----------------- | ------------------------------------------------------- |
| CustomerFormModel | Zwischenspeicherung der Eingaben in der Kundenmaske |
| ProductFormModel | Zwischenspeicherung der Eingaben in der Produktmaske |
| DocumentFormModel | Zwischenspeicherung der Eingaben in der Dokumentenmaske |
| ValidationResult | Rückgabe von Validierungsfehlern |
| UiMessage | Darstellung von Fehlern, Warnungen und Erfolgsmeldungen |
**6.2.1 Java nahe UI Modelle**
Die folgenden Klassen beschreiben die Datenmodelle, die innerhalb der Benutzeroberfläche verwendet werden. Sie dienen zur Zwischenspeicherung von Formulareingaben und zur Übergabe an Validierungs und Serviceklassen.
public class CustomerFormModel {
private String name;
private String anschrift;
private String steuernummer;
private String email;
private String telefon;
}
public class ProductFormModel {
private String bezeichnung;
private BigDecimal nettoPreis;
private BigDecimal mehrwertsteuerSatz;
private String beschreibung;
private int bestand;
}
public class DocumentFormModel {
private String customerId;
private List&lt;DocumentPositionFormModel&gt; positionen;
private String status;
}
public class DocumentPositionFormModel {
private String productId;
private int menge;
}
public class ValidationResult {
private boolean valid;
private List&lt;String&gt; messages;
}
Diese Modelle dienen nur der Benutzeroberfläche. Die dauerhafte Speicherung und fachliche Verarbeitung erfolgen in den jeweiligen Services.
**6.3 Schnittstellen des UI Moduls**
Das UI Modul kommuniziert mit mehreren fachlichen Services. Diese Services übernehmen die eigentliche Verarbeitung, Speicherung und Ausgabe der Daten.
**6.3.1 Java nahe Schnittstellendefinitionen**
Die folgenden Schnittstellen beschreiben, wie das UI Modul mit den fachlichen Komponenten kommuniziert. Die Angaben sind Java nah formuliert, damit sie als Grundlage für die spätere Implementierung und für Modultests verwendet werden können.
public interface CustomerService {
List&lt;CustomerDto&gt; loadCustomers();
CustomerDto saveCustomer(CustomerFormModel model);
CustomerDto updateCustomer(String customerId, CustomerFormModel model);
}
public interface ProductService {
List&lt;ProductDto&gt; loadProducts();
ProductDto saveProduct(ProductFormModel model);
ProductDto updateProduct(String productId, ProductFormModel model);
}
public interface DocumentService {
List&lt;DocumentDto&gt; loadDocuments();
OfferDto createOffer(DocumentFormModel model);
InvoiceDto createInvoiceFromOffer(String offerId);
DocumentDto loadDocument(String documentId);
}
public interface ValidationService {
ValidationResult validateCustomer(CustomerFormModel model);
ValidationResult validateProduct(ProductFormModel model);
ValidationResult validateDocument(DocumentFormModel model);
}
public interface PdfExportService {
Path exportDocumentAsPdf(String documentId, Path targetPath);
}
Die Benutzeroberfläche greift nicht direkt auf die lokale Speicherung zu. Sie ruft stattdessen Services auf und erhält Datenobjekte oder Validierungsergebnisse zurück.
| **ID** | **Schnittstelle** | **Zweck** |
| -------- | ----------------- | ---------------------------------------------- |
| UI-IF-01 | CustomerService | Speichern, Laden und Anzeigen von Kundendaten |
| UI-IF-02 | ProductService | Speichern, Laden und Anzeigen von Produktdaten |
| UI-IF-03 | DocumentService | Erstellen und Anzeigen von Dokumenten |
| UI-IF-04 | PdfExportService | Export eines Dokuments als PDF Datei |
| UI-IF-05 | ValidationService | Prüfung von Eingaben vor dem Speichern |
**6.4 Eingabeformate und Datentypen**
Die Oberfläche muss die Eingaben des Anwenders prüfen, bevor sie an die fachlichen Services übergeben werden.
| **Feld** | **Datentyp** | **Validierung** |
| ------------------ | ------------ | -------------------------------- |
| name | String | darf nicht leer sein |
| anschrift | String | darf nicht leer sein |
| email | String | optional, einfache Formatprüfung |
| telefon | String | optional |
| bezeichnung | String | darf nicht leer sein |
| nettoPreis | BigDecimal | muss größer oder gleich 0 sein |
| mehrwertsteuerSatz | BigDecimal | muss größer oder gleich 0 sein |
| bestand | int | darf nicht negativ sein |
| menge | int | muss größer als 0 sein |
| status | String | nur erlaubte Statuswerte |
**6.5 Rückmeldungen an die Oberfläche**
Die Services geben Rückmeldungen an die Benutzeroberfläche zurück. Diese Rückmeldungen werden in der Oberfläche als Erfolgs-, Warn- oder Fehlermeldung angezeigt.
| **Rückmeldung** | **Bedeutung** | **Anzeige in der Oberfläche** |
| --------------- | --------------------------------------- | ----------------------------- |
| success | Aktion war erfolgreich | Erfolgsmeldung |
| validationError | Eingabe ist unvollständig oder ungültig | Fehlermeldung am Formular |
| warning | Aktion ist möglich, aber kritisch | Warnmeldung |
| locked | Dokument ist schreibgeschützt | Hinweis auf Finalisierung |
| exportFinished | PDF Export wurde abgeschlossen | Erfolgsmeldung |
**7\. Systemarchitektur**
**7.1 Beschreibung der Architektur**
Das UI Modul wird in mehrere Komponenten aufgeteilt. Das Hauptfenster verwaltet die Navigation. Die einzelnen Views stellen die Masken für Kunden, Produkte und Dokumente bereit. Controller oder ViewModels verbinden die Oberfläche mit den fachlichen Services.
Die Benutzeroberfläche führt selbst keine dauerhafte Speicherung durch. Sie nimmt Eingaben entgegen, zeigt Daten an, prüft Eingaben und übergibt gültige Benutzeraktionen an die zuständigen Services.
Die Architektur besteht aus folgenden Bereichen:
| **Bereich** | **Aufgabe** |
| -------------------- | ------------------------------------- |
| MainWindow | Startpunkt der Oberfläche |
| NavigationController | Steuerung der Navigation |
| CustomerView | Anzeige und Eingabe von Kundendaten |
| ProductView | Anzeige und Eingabe von Produktdaten |
| DocumentView | Anzeige und Erstellung von Dokumenten |
| ValidationService | Prüfung von Eingaben |
| MessageService | Anzeige von Rückmeldungen |
| Fachliche Services | Verarbeitung, Speicherung und Export |
**7.2 UML Klassendiagramm**
**Abbildung 1: UML Klassendiagramm des UI Moduls**
Abbildung 1 zeigt die wichtigsten Klassen des UI Moduls und deren Beziehungen zu den fachlichen Services.
**7.3 Textuelle Beschreibung zum Klassendiagramm**
Das Hauptfenster ist der Einstiegspunkt der Anwendung. Über den NavigationController kann der Anwender zwischen Kundenansicht, Produktansicht und Dokumentenansicht wechseln.
Die CustomerView ist für die Anzeige und Eingabe von Kundendaten zuständig. Die ProductView übernimmt die Anzeige und Eingabe von Produktdaten. Die DocumentView ist für Angebote, Auftragsbestätigungen, Lieferscheine, Rechnungen und den PDF Export zuständig.
Die FormModels speichern die Eingaben aus den Formularen vorübergehend. Der ValidationService prüft diese Eingaben vor dem Speichern. Das Ergebnis der Prüfung wird als ValidationResult zurückgegeben.
Der MessageService zeigt Erfolgsmeldungen, Warnungen und Fehlermeldungen an. Die fachlichen Services übernehmen die eigentliche Verarbeitung der Daten.
**7.4 UML Sequenzdiagramm**
**Abbildung 2: UML Sequenzdiagramm zur Rechnungserstellung**
Abbildung 2 zeigt beispielhaft den Ablauf, wenn ein Anwender über die Benutzeroberfläche eine Rechnung aus einem vorhandenen Angebot erzeugt.
**7.5 Textuelle Beschreibung zum Sequenzdiagramm**
Der Anwender öffnet zuerst über das Hauptfenster die Dokumentenansicht. Die DocumentView fordert daraufhin über den DocumentService die vorhandenen Dokumente an. Anschließend werden die Dokumente in der Dokumentenübersicht angezeigt.
Danach wählt der Anwender ein vorhandenes Angebot aus und klickt auf die Aktion zur Rechnungserstellung. Die DocumentView prüft die Auswahl über den ValidationService. Wenn die Auswahl gültig ist, wird über den DocumentService eine Rechnung erzeugt.
Nach erfolgreicher Erstellung zeigt der MessageService eine Erfolgsmeldung in der Benutzeroberfläche an. Dadurch erhält der Anwender eine direkte Rückmeldung, dass die Rechnung erfolgreich erzeugt wurde.
**8\. Abnahmekriterien**
Die Abnahmekriterien legen fest, wann das Modul Benutzeroberfläche als erfolgreich umgesetzt gilt. Sie beziehen sich auf die funktionalen und nichtfunktionalen Anforderungen dieses Pflichtenhefts.
| **ID** | **Bezug** | **Abnahmekriterium** | **Testmethode** |
| -------- | --------- | -------------------------------------------------------------------------------------------------- | ----------------- |
| AK-UI-01 | PH-UI-01 | Das Hauptfenster startet und zeigt eine Navigation zu Kunden, Produkten und Dokumenten an. | Manueller UI Test |
| AK-UI-02 | PH-UI-02 | Ein Kunde kann über die Kundenmaske eingegeben und gespeichert werden. | Funktionstest |
| AK-UI-03 | PH-UI-03 | Ein Produkt kann über die Produktmaske eingegeben und gespeichert werden. | Funktionstest |
| AK-UI-04 | PH-UI-04 | Vorhandene Dokumente werden in einer Tabelle angezeigt. | UI Test |
| AK-UI-05 | PH-UI-05 | Ein Angebot kann über die Dokumentenmaske mit Kunde und mindestens einer Position erstellt werden. | Workflow Test |
| AK-UI-06 | PH-UI-06 | Eine Rechnung kann aus einem vorhandenen Angebot über eine sichtbare Aktion erzeugt werden. | Workflow Test |
| AK-UI-07 | PH-UI-07 | Fehlende Pflichtfelder verhindern das Speichern und werden sichtbar markiert. | Negativtest |
| AK-UI-08 | PH-UI-08 | Erfolgs und Fehlermeldungen werden verständlich angezeigt. | UI Test |
| AK-UI-09 | PH-UI-09 | Finalisierte Rechnungen sind in der Oberfläche schreibgeschützt. | Funktionstest |
| AK-UI-10 | PH-UI-10 | Der PDF Export kann über einen sichtbaren Button gestartet werden. | Funktionstest |
| AK-UI-11 | PH-Q-01 | Ein neuer Anwender kann die Kernprozesse innerhalb von maximal 10 Minuten durchführen. | Usability Test |
| AK-UI-12 | PH-Q-05 | Die Oberfläche ist ohne Internetverbindung nutzbar. | Systemtest |
**9\. Traceability Matrix**
Die Traceability Matrix zeigt, welche Anforderungen aus dem Lastenheft durch welche Anforderungen im Pflichtenheft umgesetzt werden. Dadurch ist nachvollziehbar, dass die Benutzeroberfläche die geforderten Funktionen sichtbar unterstützt.
| **Lastenheft ID** | **Inhalt aus dem Lastenheft** | **Pflichtenheft ID** |
| ----------------- | ------------------------------------------------------------- | ---------------------------- |
| BA-01 | Kundendatensätze anlegen | PH-UI-02, PH-UI-07, PH-UI-08 |
| BA-02 | Produktdaten speichern | PH-UI-03, PH-UI-07, PH-UI-08 |
| BA-03 | Angebot für vorhandenen Kunden mit Produktposition erstellen | PH-UI-05, PH-UI-07, PH-UI-08 |
| BA-04 | Rechnung aus vorhandenem Angebot erzeugen | PH-UI-06, PH-UI-09 |
| Q-01 | Benutzerfreundlichkeit und Bedienung innerhalb von 10 Minuten | PH-Q-01, PH-Q-02, PH-Q-03 |
| Q-02 | Datensicherheit und zuverlässige Datenverfügbarkeit | PH-UI-07, PH-Q-04, PH-Q-05 |
| Q-03 | Revisionssicherheit finaler Rechnungen | PH-UI-09 |
| IF-01 | Benutzerschnittstelle GUI | PH-UI-01 bis PH-UI-10 |
| IF-03 | Druck und Export Schnittstelle | PH-UI-10 |
| GR-02 | Unveränderlichkeit finaler Belege | PH-UI-09 |
| GR-05 | Dokumentenketten Konsistenz | PH-UI-06 |
| GR-06 | Summenberechnung | PH-UI-05 |
**10\. Modultestplan**
Der Modultestplan beschreibt Testfälle für das Modul Benutzeroberfläche. Die Testfälle prüfen, ob die Oberfläche die geforderten Funktionen korrekt anzeigt, Eingaben richtig behandelt und dem Anwender verständliche Rückmeldungen gibt.
Die Tests können manuell über die Oberfläche durchgeführt werden. Einzelne Prüfungen, zum Beispiel Validierungen, können zusätzlich automatisiert auf Controller, ViewModel oder Service Ebene getestet werden.
| **Testfall ID** | **Anforderung** | **Testziel** | **Vorbedingung** | **Testschritte** | **Erwartetes Ergebnis** |
| --------------- | ------------------ | ------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| MT-UI-01 | PH-UI-01 | Start des Hauptfensters prüfen | Anwendung ist installiert | Anwendung starten | Hauptfenster wird angezeigt und Navigation ist sichtbar |
| MT-UI-02 | PH-UI-01 | Navigation zur Kundenmaske prüfen | Hauptfenster ist geöffnet | Menüpunkt Kunden anklicken | Kundenübersicht wird angezeigt |
| MT-UI-03 | PH-UI-01 | Navigation zur Produktmaske prüfen | Hauptfenster ist geöffnet | Menüpunkt Produkte anklicken | Produktübersicht wird angezeigt |
| MT-UI-04 | PH-UI-02 | Kunde mit gültigen Daten speichern | Kundenmaske ist geöffnet | Name und Anschrift eingeben, Speichern anklicken | Kunde wird gespeichert und Erfolgsmeldung erscheint |
| MT-UI-05 | PH-UI-02, PH-UI-07 | Kunde ohne Pflichtfeld ablehnen | Kundenmaske ist geöffnet | Name oder Anschrift leer lassen, Speichern anklicken | Speicherung wird verhindert und Fehlermeldung erscheint |
| MT-UI-06 | PH-UI-03 | Produkt mit gültigen Daten speichern | Produktmaske ist geöffnet | Bezeichnung, Preis, Steuersatz und Bestand eingeben, Speichern anklicken | Produkt wird gespeichert und Erfolgsmeldung erscheint |
| MT-UI-07 | PH-UI-03, PH-UI-07 | Negativen Produktpreis ablehnen | Produktmaske ist geöffnet | Negativen Preis eingeben, Speichern anklicken | Speicherung wird verhindert und Fehlermeldung erscheint |
| MT-UI-08 | PH-UI-03, PH-UI-07 | Negativen Bestand ablehnen | Produktmaske ist geöffnet | Negativen Bestand eingeben, Speichern anklicken | Speicherung wird verhindert und Fehlermeldung erscheint |
| MT-UI-09 | PH-UI-04 | Dokumentenübersicht anzeigen | Es existiert mindestens ein Dokument | Dokumentenbereich öffnen | Dokumente werden tabellarisch angezeigt |
| MT-UI-10 | PH-UI-05 | Angebot mit Kunde und Position erstellen | Kunde und Produkt existieren | Kunde auswählen, Produktposition hinzufügen, Speichern anklicken | Angebot wird erstellt und Summenbereich wird angezeigt |
| MT-UI-11 | PH-UI-05, PH-UI-07 | Angebot ohne Produktposition ablehnen | Kunde existiert | Kunde auswählen, keine Position hinzufügen, Speichern anklicken | Speicherung wird verhindert und Fehlermeldung erscheint |
| MT-UI-12 | PH-UI-06 | Rechnung über Oberfläche erzeugen | Passendes Angebot existiert | Dokument öffnen, Aktion für Rechnungserstellung anklicken | Rechnung wird erzeugt und Erfolgsmeldung erscheint |
| MT-UI-13 | PH-UI-08 | Erfolgsmeldung prüfen | Gültige Eingabe liegt vor | Daten speichern | Verständliche Erfolgsmeldung wird angezeigt |
| MT-UI-14 | PH-UI-08 | Fehlermeldung prüfen | Ungültige Eingabe liegt vor | Speichern anklicken | Verständliche Fehlermeldung wird angezeigt |
| MT-UI-15 | PH-UI-09 | Schreibschutz finalisierter Rechnung prüfen | Finalisierte Rechnung existiert | Rechnung öffnen und Bearbeitung versuchen | Eingabefelder sind deaktiviert und Rechnung kann nicht geändert werden |
| MT-UI-16 | PH-UI-10 | PDF Export starten | Dokument existiert | Dokument öffnen, Export Button anklicken | Dialog zur Auswahl des Speicherorts wird geöffnet |
| MT-UI-17 | PH-Q-01 | Bedienbarkeit prüfen | Testperson kennt das System nicht | Testperson legt Kunde, Produkt und Dokument an | Aufgaben werden innerhalb von maximal 10 Minuten ohne Hilfe abgeschlossen |
| MT-UI-18 | PH-Q-05 | Offline Nutzung prüfen | Internetverbindung ist deaktiviert | Anwendung starten und Hauptfunktionen öffnen | Oberfläche ist ohne Internetverbindung nutzbar |
**10.1 Testdaten**
Für die Modultests werden folgende Beispieldaten verwendet.
| **Objekt** | **Testdaten** |
| ------------------ | ------------------------------------------------------------------------------ |
| Kunde | Max Mustermann, Musterstraße 1, 68161 Mannheim |
| Produkt | Beratung, Netto Einzelpreis 100,00 Euro, Mehrwertsteuer 19 Prozent, Bestand 10 |
| Dokumentposition | Produkt Beratung, Menge 2 |
| Rechnung | Finalisierte Rechnung mit Rechnungsnummer 1 |
| Ungültiger Preis | minus 10,00 Euro |
| Ungültiger Bestand | minus 5 |
| Ungültige Menge | 0 |
**10.2 Bewertung der Testergebnisse**
Ein Testfall gilt als bestanden, wenn das erwartete Ergebnis vollständig eintritt. Ein Testfall gilt als nicht bestanden, wenn eine Funktion nicht erreichbar ist, eine falsche Meldung angezeigt wird oder ungültige Eingaben gespeichert werden können.
Fehlgeschlagene Testfälle werden dokumentiert und nach der Fehlerbehebung erneut durchgeführt.
**11\. Glossar**
| **Begriff** | **Erklärung** |
| --------------------- | ------------------------------------------------------------------------------------------------- |
| Benutzeroberfläche | Grafische Oberfläche, über die der Anwender mit dem System arbeitet |
| UI | User Interface, also Benutzeroberfläche |
| GUI | Graphical User Interface, grafische Benutzeroberfläche |
| Maske | Eingabebereich für bestimmte Daten, zum Beispiel Kundenmaske oder Produktmaske |
| Pflichtfeld | Feld, das ausgefüllt werden muss, damit eine Eingabe gespeichert werden kann |
| Validierung | Prüfung, ob eine Eingabe korrekt und vollständig ist |
| Fehlermeldung | Hinweis, dass eine Eingabe oder Aktion nicht gültig ist |
| Erfolgsmeldung | Hinweis, dass eine Aktion erfolgreich abgeschlossen wurde |
| Warnmeldung | Hinweis auf eine mögliche kritische Situation |
| Schreibschutz | Zustand, bei dem Daten nicht mehr verändert werden können |
| Finalisierte Rechnung | Rechnung, die abgeschlossen ist und nicht mehr verändert werden darf |
| Dokumentenworkflow | Ablauf, bei dem aus einem Dokument ein Folgedokument entsteht |
| Angebot | Dokument, mit dem einem Kunden eine Leistung oder Ware angeboten wird |
| Auftragsbestätigung | Dokument, das einen angenommenen Auftrag bestätigt |
| Lieferschein | Dokument, das die Lieferung von Waren oder Leistungen dokumentiert |
| Rechnung | Dokument, mit dem eine Zahlung gefordert wird |
| Medienbruch | Unterbrechung eines Prozesses durch Wechsel in ein anderes System oder durch manuelle Übertragung |
| View | Sichtbarer Bereich der Benutzeroberfläche |
| Service | Fachliche Komponente, die Daten verarbeitet oder speichert |
| ViewModel | Objekt, das Daten zwischen Oberfläche und Logik vorbereitet |
| PDF Export | Erstellung einer PDF Datei aus einem Dokument |
| Traceability Matrix | Tabelle, die Anforderungen aus dem Lastenheft mit Anforderungen aus dem Pflichtenheft verbindet |
| Abnahmekriterium | Bedingung, anhand der geprüft wird, ob eine Anforderung erfüllt ist |
| Modultest | Test eines einzelnen Systembereichs, hier des UI Moduls |
| Usability Test | Test, bei dem geprüft wird, ob ein Anwender die Oberfläche einfach bedienen kann |

View File

@ -0,0 +1,516 @@
Software Engineering 1 | Pflichtenheft | Gruppe J | Kundenverwaltung
Frakturierungssystem
Datum: 15.06.2026 | Version: 1.0
Weiterleitung zum Git: <https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3>
Inhalt
[Dokumentenhistorie 3](#_Toc232257228)
[1\. Einleitung und Zielsetzung 3](#_Toc232257229)
[1.1 Zweck des Dokuments 3](#_Toc232257230)
[1.2 Ziel 3](#_Toc232257231)
[1.3 Geltungsbereich 4](#_Toc232257232)
[1.4 Definitionen und Abkürzungen 4](#_Toc232257233)
[1.5 Referenzen 4](#_Toc232257234)
[2\. Systemüberblick 5](#_Toc232257235)
[2.1 Beschreibung 5](#_Toc232257236)
[2.2 Abgrenzung (Was gehört dazu / was nicht) 5](#_Toc232257237)
[2.3 Grobe Systemfunktionen 5](#_Toc232257238)
[2.4 UML-Bezug 6](#_Toc232257239)
[3\. Stakeholder und Kontext 6](#_Toc232257240)
[3.2 Akteur 6](#_Toc232257241)
[3.3 Angrenzende Komponente 6](#_Toc232257242)
[4\. Funktionale Anforderungen 7](#_Toc232257243)
[4.1 Kunden anlegen (aus BA-01) 7](#_Toc232257244)
[4.2 Kunde bearbeiten (aus BA-01) 7](#_Toc232257245)
[4.3 Kunde abrufen (aus BA-01) 8](#_Toc232257246)
[4.4 Kunde löschen (aus BA-01, GR-04) 8](#_Toc232257247)
[4.5 Transaktionshistorie (aus BA-01) 8](#_Toc232257248)
[5\. Nicht-funktionale Anforderungen 8](#_Toc232257249)
[5.1 Benutzerfreundlichkeit (aus Q-01) 8](#_Toc232257250)
[5.2 Datensicherheit und Zuverlässigkeit (aus Q-02) 8](#_Toc232257251)
[5.3 Revisionssicherheit (aus Q-03) 9](#_Toc232257252)
[5.4 Performance 9](#_Toc232257253)
[6\. Daten und Schnittstellen 9](#_Toc232257254)
[6.1 Datenobjekte und Datentypen 9](#_Toc232257255)
[6.2 Schnittstellen 10](#_Toc232257256)
[6.3 Geschäftsregeln mit Datenbezug 11](#_Toc232257257)
[7\. Systemarchitektur (logisch, grob) 12](#_Toc232257258)
[7.1 Klassendiagramm 12](#_Toc232257259)
[7.2 Sequenzdiagramm 12](#_Toc232257260)
[8\. Testbare Abnahmekriterien 13](#_Toc232257261)
[9\. Traceability LH &lt;-&gt; PH 15](#_Toc232257262)
[10\. Modultestplan 16](#_Toc232257263)
[11\. Anhänge 18](#_Toc232257264)
[11.1 Abkürzungen 18](#_Toc232257265)
[11.2 Glossar 18](#_Toc232257266)
Freigabeübersicht
| Autor | Freigebenden | Prüfer |
| ----------------------------------------------------------- | ----------------------- | ----------------------- |
| Khazanovych, Christian <br>Winkler, Louis <br>Senger, Robin | Prof. Dr. Marmitt, Gerd | Prof. Dr. Marmitt, Gerd |
| Gruppe J | Modulverantwortlicher | Modulverantwortlicher |
| 15.06.2026 | 15.06.2026 | 15.06.2026 |
# Dokumentenhistorie
| Version | Datum | Autor | Grund der Änderung |
| ------- | ---------- | ----------------------------------------------------------- | ---------------------------------------- |
| 1.0 | 15.06.2026 | Khazanovych, Christian <br>Winkler, Louis <br>Senger, Robin | Erstmalige Erstellung des Pflichtenhefts |
# Einleitung und Zielsetzung
## 1.1 Zweck des Dokuments
Dieses Dokument beschreibt auf technischer Ebene, wie die Anforderungen aus dem Lastenheft (Team 3, Version 1.4) für den Bereich Kundenverwaltung konkret umgesetzt werden. Während das Lastenheft festhält, was das System leisten soll, gibt dieses Pflichtenheft die Antwort auf das wie. Die hier festgelegten Systemanforderungen sind direkt prüfbar und fließen unmittelbar in die Implementierung sowie den abschließenden Modultest ein.
## 1.2 Ziel
Im Fokus steht die lückenlose Spezifikation aller Funktionen rund um die Verwaltung von Kundendaten, das Anlegen neuer Datensätze, das Bearbeiten bestehender Einträge, das Abrufen gespeicherter Informationen sowie das regelkonforme Löschen unter Berücksichtigung der Geschäftsregel GR-04 (referenzielle Integrität). Darüber hinaus wird definiert, wie diese Daten anderen Komponente, insbesondere dem Belegworkflow (Gruppe K), zugänglich gemacht werden, da jedes kaufmännische Dokument zwingend auf einem gültigen Kundendatensatz beruht.
## 1.3 Geltungsbereich
Dieses Pflichtenheft bezieht sich ausschließlich auf die Komponente Gruppe J - Kundenverwaltung. Die Gesamtanwendung wird in vier unabhängigen Teilbereiche entwickelt, für die jeweils ein eigenes Pflichtenheft existiert:
| Gruppe | Komponente | Pflichtenheft |
| ------ | ------------------ | --------------- |
| I | Benutzeroberfläche | / |
| J | Kundenverwaltung | Dieses Dokument |
| K | Belegworkflow | / |
| L | Bestandsmanagement | / |
Themen wie die Produktverwaltung, der Aufbau der Benutzeroberfläche oder die Logik zur Beleggenerierung werden hier nicht behandelt. Die Komponente J stellt ihre Daten über eine klar definierte interne Schnittstelle bereit (siehe Kapitel 6.2), greift auf andere Komponente jedoch nicht aktiv zu.
## 1.4 Definitionen und Abkürzungen
Alle im Lastenheft (Abschnitt 8) eingeführten Begriffe und Kürzel gelten unverändert weiter und werden an dieser Stelle nicht wiederholt. Abkürzungen, die spezifisch für dieses Pflichtenheft sind, finden sich gesammelt in Kapitel 11.
## 1.5 Referenzen
| Dokument / Quelle | Details & Version | Datum |
| ------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------- |
| Projekt Charter | Team 3, Version 1.2 | 15.04.2026 |
| Lastenheft „Fakturierungsanwendung" | Team 3, Version 1.4 | 15.05.2026 |
| Pflichtenheft | Gruppe J, Version 1.0 | 15.06.2026 |
| Vorlesungsunterlagen Software Engineering 1 | Hochschule Mannheim, Folienblock „Lasten- und Pflichtenheft" | Sommersemester 2026 |
| GoBD | Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern in elektronischer Form | i. d. g. F. (in der geltenden Fassung) |
| DSGVO | Verordnung (EU) 2016/679 (Datenschutz-Grundverordnung) | i. d. g. F. |
| § 14 UstG | Pflichtangabe einer Rechnung | i. d. g. F. |
# Systemüberblick
## 2.1 Beschreibung
Die Kundenverwaltung ist eine von vier Komponente der Fakturierungsanwendung und bildet das datentechnische Fundament des Gesamtsystems. Sie läuft vollständig lokal auf dem Rechner des Anwenders, benötigt keine Internetverbindung und speichert alle Daten auf dem lokalen Dateisystem. Über die grafische Oberfläche (Gruppe I) kann der Anwender Kundendatensätze anlegen, einsehen, bearbeiten und sofern keine aktiven Dokumentenreferenzen bestehen auch löschen. Andere Komponenten, insbesondere der Belegworkflow (Gruppe K), greifend lesend auf die hier verwalteten Daten zu.
## 2.2 Abgrenzung (Was gehört dazu / was nicht)
Im Umfang dieser Anwendung (In-Scope):
- Anlegen neuer Kundendatensätze mit allen relevanten Stammdaten
- Bearbeiten und Abrufen bestehender Einträge
- Löschen von Kundendatensätzen unter Prüfung referenzieller Integrität (GR-04)
- Bereitstellung der Kundendaten über eine interne Schnittstelle für andere Komponenten
- Anzeigen einer vollständigen Transaktionshistorie je Kunde
Explizit nicht im Umfang der Anwendung (Out-of-Scope / Nicht-Ziele):
- Verwaltung von Produkten oder Lagerbeständen (Gruppe L)
- Erstellung oder Verwaltung von Belegen jeglicher Art (Gruppe K)
- Aufbau und Layout der grafischen Oberfläche (Gruppe I)
- Cloud-Synchronisation, Netzwerkzugriff oder externe Datenbankanbindung (Lastenheft, Abschnitt 1.3)
## 2.3 Grobe Systemfunktionen
Der grundlegende Ablauf innerhalb der Komponente lässt sich wie folgt zusammenfassen:
Kunde anlegen -> Pflichtfelder validieren -> Datensatz persistieren -> In Übersicht anzeigen
Kunden bearbeiten -> Bestehenden Datensatz laden -> Änderungen validieren -> aktualisiert speichern
Kunde löschen -> Referenzprüfung (GR-04) -> Bei Freigabe löschen, sonst Hinweis ausgeben
Kundendaten abrufen -> Über interne Schnittstelle an anfragende Komponente zurückgeben
## 2.4 UML-Bezug
Die für diese Komponente relevanten Use Cases sind: Kunde anlegen, Kunde bearbeiten, Kunde löschen und Kundendaten abrufen. Das detaillierte Klassendiagramm sowie ein Sequenzdiagramm für den Ablauf „Kunden anlegen" folgen in Kapitel 7.
# Stakeholder und Kontext
Stakeholder und Systemkontext sind im Lastenheft (Abschnitt 2 und 3) grundlegend beschrieben und behalten dort ihre Gültigkeit. Für die Komponente Kundenverwaltung sind folgende Parteien besonders relevant:
| ID | Stakeholder | Relevanz für diese Komponente |
| ----- | -------------------------------- | --------------------------------------------------------------------------------------------------- |
| SH-01 | Auftraggeber (Prof. Dr. Marmitt) | Definiert die fachlichen Anforderungen und bewertet die Umsetzung anhand der Abnahmekriterien |
| SH-02 | Projektteam (Team 3) | Verantwortlich für die technische Umsetzung, andere Gruppe sind auf korrekte Kundendaten angewiesen |
| SH-03 | Endnutzer | Nutzt die Kundenverwaltung direkt, um Datensätze zu erfassen und für Belege bereitzustellen |
## 3.2 Akteur
Da die Anwendung explizit für den Einzelplatzbetrieb konzipiert ist, gibt es genau einen handelnden Akteur in dieser Komponente:
Einzelanwender:in - Die natürliche Person (z.B. Freiberufler:in oder Inhaber:in eines Kleinstunternehmens), die Kundendaten eigenverantwortlich erfasst, pflegt und für die Belegausstellung verwendet
## 3.3 Angrenzende Komponente
Die Kundenverwaltung steht mit folgenden Teilen des Gesamtsystems in Beziehung:
| Komponente | Gruppe | Art der Beziehung |
| ------------------- | ------ | ------------------------------------------------------------------------------------------ |
| Benutzeroberfläche | I | Gruppe I stellt die GUI bereit, über die der Anwender mit der Kundenverwaltung interagiert |
| Belegworkflow | K | Gruppe K greift lesend auf Kundendaten zu, um Belege korrekt adressieren zu können |
| Bestandsmanagement | L | Keine direkte Abhängigkeit, beide Komponente liefern unabhängig voneinander Stammdaten |
| Lokales Dateisystem | \- | Persistente Speicherung aller Kundendatensätze auf dem Rechner des Anwenders |
# Funktionale Anforderungen
Die funktionale Anforderungen beschreiben auf Systemebene, wie die Kundenverwaltung die im Lastenheft festgelegten Anforderungen BA-01 technisch umsetzt.
## 4.1 Kunden anlegen (aus BA-01)
- F-01: Das System MUSS es der Anwender:in ERMÖGLICHEN, einen neuen Kundendatensatz mit folgenden Pflichtfeldern anzulegen: Name oder Firmenname, vollständige Anschrift, Steuernummer oder USt-IdNr., E-Mail-Adresse und Telefonnummer.
- F-02: WENN ein neuer Kundendatensatz gespeichert wird, DANN MUSS das System automatisch eine eindeutige, fortlaufende Kunden-ID vergeben, die nachträglich nicht verändert werden kann.
- F-03: WENN ein Pflichtfeld beim Speichern leer ist, DANN MUSS das System den Speichervorgang ablehnen und dem Anwender klar anzeigen, welches Feld fehlt.
- F-04: WENN ein Kundendatensatz erfolgreich gespeichert wurde, DANN MUSS der Kunde unmittelbar in der Kundenübersicht erscheinen und für andere Komponenten (insbesondere den Belegworkflow, Gruppe K) abrufbar sein.
## 4.2 Kunde bearbeiten (aus BA-01)
- F-05: Das System MUSS es der Anwender:in ERMÖGLICHEN, alle Felder eines bestehenden Kundendatensatzes, mit Ausnahme der Kunden-ID, nachträglich zu bearbeiten und zu speichern.
- F-06: WENN eine Änderung an einem Kundendatensatz gespeichert wird, DANN MUSS das System sicherstellen, dass alle Pflichtfelder weiterhin befüllt sind, bevor die Aktualisierung übernommen wird.
- F-07: WENN ein Kundendatensatz bearbeitet wurde, DANN MUSS die aktualisierte Version in der Kundenübersicht und über die interne Schnittstelle sofort verfügbar sein, ohne dass ein Neustart der Anwendung erforderlich ist.
## 4.3 Kunde abrufen (aus BA-01)
- F-08: Das System MUSS es der Anwender:in ERMÖGLICHEN, die vollständige Kundenliste in einer übersichtlichen Listenansicht einzusehen.
- F-09: Das System MUSS eine Suchfunktion bereitstellen, mit der Kundendatensätze anhand von Name oder Kunden-ID gefunden werden können.
- F-10: Das System MUSS über eine interne Schnittstelle (KundenService) anderen Komponenten den lesenden Zugriff auf einzelne Kundendatensätze per Kunden-ID ermöglichen, ohne dass diese Komponenten direkten Zugriff auf die Datenhaltung benötigen.
## 4.4 Kunde löschen (aus BA-01, GR-04)
- F-11: Das System MUSS es der Anwender:in ERMÖGLICHEN, einen Kundendatensatz zu löschen, sofern dieser in keinem aktiven oder archivierten Beleg referenziert wird.
- F-12: WENN ein Löschversuch für einen Kunden ausgeführt wird, der in mindestens einem Beleg referenziert ist, DANN MUSS das System den Löschvorgang verweigern und eine verständliche Fehlermeldung ausgeben, die den Grund der Ablehnung benennt (GR-04).
## 4.5 Transaktionshistorie (aus BA-01)
- F-13: Das System MUSS je Kundendatensatz eine lückenlose Übersicht aller verknüpften Belege (Angebote und Rechnungen) anzeigen können, sodass die gesamte Geschäftsbeziehung nachvollziehbar bleibt.
# Nicht-funktionale Anforderungen
## 5.1 Benutzerfreundlichkeit (aus Q-01)
- NF-USE-01: Das Anlegen eines neuen Kundendatensatzes MUSS von einer Person ohne technische Vorkenntnisse und ohne vorherige Einweisung innerhalb von maximal 3 Minuten erfolgreich abgeschlossen werden können. Der Nachweis erfolgt durch einen manuellen Usability-Test mit mindestens 5 Testpersonen.
- NF-USE-02: WENN ein Pflichtfeld nicht ausgefüllt wurde, MUSS das System die fehlerhafte oder fehlende Eingabe so hervorheben, dass mindestens 80 % der Testpersonen die Korrektur im ersten Versuch ohne fremde Hilfe vornehmen können.
## 5.2 Datensicherheit und Zuverlässigkeit (aus Q-02)
- NF-SEC-01: Das System MUSS sicherstellen, dass alle gespeicherten Kundendatensätze nach einem Neustart der Anwendung vollständig und unverändert wieder zur Verfügung stehen. Ein Datenverlust durch reguläres Beenden der Anwendung ist nicht zulässig.
- NF-SEC-02: Die Datenhaltung MUSS ausschließlich lokal auf dem Dateisystem des Anwenders erfolgen. Eine Übertragung von Kundendaten an externe Dienste oder Dritte ist systemseitig nicht vorgesehen und technisch auszuschließen (DSGVO-Konformität).
## 5.3 Revisionssicherheit (aus Q-03)
- NF-INT-01: Kundendatensätze, die in einem finalisierten Beleg referenziert werden, MÜSSEN vor versehentlichem Löschen geschützt sein. Das System MUSS einen entsprechenden Löschversuch erkennen und aktiv verweigern, ohne dass Daten verloren gehen (GR-04).
## 5.4 Performance
- NF-PERF-01: Suchanfragen innerhalb der Kundenliste MÜSSEN bei einem Datenbestand von bis zu 5.000 Kundendatensätzen in unter 1 Sekunde ein Ergebnis liefern.
- NF-PERF-02: Das Speichern sowie das Laden eines Kundendatensatzes MUSS in unter 2 Sekunden abgeschlossen sein, unabhängig von der Gesamtgröße des Datenbestands.
# Daten und Schnittstellen
Dieses Kapitel bildet die technische Grundlage für den Modultestplan (Kapitel 10). Alle Datentypen werden bereits als Java-Typen angegeben, da das Pflichtenheft direkt Input für die Implementierung und den Komponententest ist.
## 6.1 Datenobjekte und Datentypen
Designgrundsätze zur Datentyp-Wahl:
- Kundenummer werden als String geführt, da sie ein festes Format mit führenden Nullen und Präfix besitzen. Ein ganzzahliger Typ würde führende Nullen verlieren
- Alle Pflichtfelder werden als String geführt
- Die Kunden-ID wird vom System automatisch vergeben und ist unveränderlich
Klasse: Kunde
Repräsentiert eine einzelne Position innerhalb eines kaufmännischen Dokuments
| Attribut | Java-Typ | Beschreibung |
| --------------- | ------------------ | ------------------------------------------------------------------------------ |
| kundenId | String | Eindeutige, vom System generierte Kundennummer, unveränderlich nach Erstellung |
| name | String | Name der natürlichen Person oder Firmenname, Pflichtfeld |
| strasse | String | Straße und Hausnummer, Pflichtfeld |
| plz | String | Postleitzahl, Pflichtfeld |
| ort | String | Wohnort oder Firmensitz, Pflichtfeld |
| steuerNummer | String | Steuernummer oder Ust-IdNr. Gemäß § 14 UstG, Pflichtfeld |
| email | String | E-Mail-Adresse des Kunden, Pflichtfeld |
| telefon | String | Telefonnummer des Kunden, Pflichtfeld |
| belegReferenzen | List&lt;String&gt; | Liste der Belegnummern, in denen dieser Kunde referenziert wird (für GR-04) |
Enum: KundenStatus
Enum KundenStatus {AKTIV, ARCHIVIERT}
| Wert | Bedeutung |
| ---------- | ----------------------------------------------------------------------- |
| AKTIV | Kundendatensatz ist aktiv und kann für neue Belege verwendet werden |
| ARCHIVIERT | Kunde ist nicht mehr aktiv, bestehende Belegreferenzen bleiben erhalten |
## 6.2 Schnittstellen
Externe Schnittstellen:
| ID | Schnittstellen | Zweck |
| ----- | ----------------------------- | ---------------------------------------------------------------------------- |
| IF-01 | Lokales Dateisystem | Persistente Speicherung aller Kundendatensätze auf dem Rechner des Anwenders |
| IF-02 | Benutzeroberfläche (Gruppe 1) | Darstellung der Kundenliste und Eingabemasken für den Anwender |
Interne Schnittstellen (zu anderen Komponenten):
// Lesender Zugriff auf Kundenstammdaten, wird von Gruppe K (Belegworkflow) genutzt
public interface KundenService {
// Gibt einen einzelnen Kundendatensatz anhand der Kundennummer zurück
// Gibt null zurück, wenn kein Kunde mit dieser Nummer existiert
Kunde findeKunde(String kundenId);
// Gibt eine Liste aller gespeicherten Kunden zurück
List&lt;Kunde&gt; alleKunden();
// Gibt alle Kunden zurück, deren Name den Suchbegriff enthält
List&lt;Kunde&gt; sucheNachName(String suchbegriff);
}
Kundennummer-Schnittstelle (komponenteninterner Dienst):
// Generiert die nächste eindeutige, lückenlose Kundennummer
public interface KundenIdGenerator {
// Liefert die nächste verfügbare Kundennummer im Format K-XXXXXX
String naechsteKundenId();
}
## 6.3 Geschäftsregeln mit Datenbezug
Folgende Geschäftsregeln aus dem Lastenheft haben direkten Einfluss auf die Datenhaltung und müssen auf Implementierungsebene berücksichtigt werden:
| Regel | Auswirkung auf die Daten |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| GR-04 (Referenzielle Integrität) | Von jedem Löschvorgang wird belegReferenz geprüft. Ist die Liste nicht leer, wird der Löschvorgang verweigert |
| Q-02 (Datenpersistenz) | Jede Änderung an einem Kundendatensatz wird sofort auf dem lokalen Dateisystem gespeichert, nicht erst beim Beenden der Anwendung |
# Systemarchitektur (logisch, grob)
Die Komponente Kundenverwaltung folgt einer klar geschichteten Struktur. Die Benutzeroberfläche (Gruppe I) kommuniziert ausschließlich über den KundenService mit der Fachlogik. Der KundenService kapselt alle Operationen rund um Kundendaten und nutzt dabei den KundenIdGenerator für die automatische Nummernvergabe sowie das KundenRepository für die persistente Speicherung auf dem lokalen Dateisystem. Andere Komponente, insbesondere der Belegworkflow (Gruppe K), greifen ebenfalls nur über das KundenService -Interface lesend auf Kundendaten zu, ohne direkten Zugriff auf die Datenhaltung zu haben.
## 7.1 Klassendiagramm
Abbildung 1: UML-Klassendiagramm Kundenverwaltung (Gruppe J)
Beschreibung zu Abbildung 1: Das Klassendiagramm zeigt den strukturellen Aufbau der Kundenverwaltung. Die zentrale Klasse Kunde hält alle relevanten Stammdaten sowie eine Liste von Belegreferenzen, die für die Prüfung der referenziellen Integrität (GR-04) benötigt wird. Der KundenStatus wird als Enum abgebildet und unterscheidet zwischen aktiven und archivierten Kunden. Die Klasse KundenServiceImp1 implementiert das Interface KundenService und stell damit sowohl der Benutzeroberfläche als auch anderen Komponenten eine einheitliche Zugriffsebene bereit. Intern nutzt sie das KundenRepository für alles Lese- und Schreibzugriffe auf das lokale Dateisystem sowie den KundenIdGenerator für die automatische, lückenlose Vergabe von Kundennummern.
## 7.2 Sequenzdiagramm
Abbildung 2: UML-Sequenzdiagramm „Kunde anlegen" (Gruppe J)
Beschreibung zu Abbildung 2: Das Sequenzdiagramm zeigt den vollständigen Ablauf beim Anlegen eines neuen Kunden. Die Anwender:in füllt das Formular über die GUI (Gruppe I) aus und löst den Speichervorgang aus. Die GUI delegiert den Aufruf an KundenServiceImp1, welche zunächst alle Pflichtfelder über validiereFelder() prüft. Schlägt die Validierung fehl, wird eine Fehlermeldung an die GUI zurückgegeben, die dem Anwender das fehlende Feld benennt (F-03). Sind alle Felder gültig, wird über den KundenIdGenerator eine neue, eindeutige Kundennummer angefordert (F-02). Anschließend wird der vollständige Datensatz über das KundenRepository auf dem lokalen Dateisystem persistiert. Nach erfolgreicher Speicherung erscheint der neue Kunde unmittelbar in der Kundenübersicht (F-04).
# Testbare Abnahmekriterien
Die folgende Abnahmekriterien leiten sich direkt aus den funktionalen und nicht-funktionalen Anforderungen ab. Sie sind so formuliert, dass sie als Grundlage für den Modultestplan (Kapitel 10) dienen und eindeutig nachgewiesen werden können.
AC-J-01 (zu F-01, F-02, F-04) - Kunde erfolgreich anlegen
Vorbedingung: Die Anwendung ist gestartet, es sind noch keine Kunden vorhanden.
Aktion: Die Anwender:in füllt alle Pflichtfelder (Name, Anschrift, Steuernummer, E-Mail, Telefon) aus und speichert den Datensatz.
Erwartet: Der Kunde wird mit einer automatisch vergebenen, eindeutigen Kunden-ID im Format „K-XXXXXX" gespeichert und erscheint unmittelbar in der Kundenübersicht.
AC-J-02 (zu F-03, NF-USE-02) - Speichern bei fehlendem Pflichtfeld verweigern
Vorbedingung: Die Anwendung ist gestartet, das Formular zum Anlegen eines Kunden ist geöffnet.
Aktion: Die Anwender:in lässt mindestens ein Pflichtfeld leer und versucht zu speichern.
Erwartet: Der Speichervorgang wird abgelehnt. Das System hebt das fehlende Feld visuell hervor und benennt es namentlich, sodass die Korrektur ohne fremde Hilfe vorgenommen werden kann.
AC-J-03 (zu F-05, F-06, F-07) - Kundendaten bearbeiten
Vorbedingung: Mindestens ein Kundendatensatz ist gespeichert.
Aktion: Die Anwender:in öffnet den Datensatz, ändert die E-Mail-Adresse und speichert die Änderung.
Erwartet: Die aktualisierte E-Mail-Adresse ist sofort in der Kundenübersicht sowie über den KundenService abrufbar, ohne dass ein Neustart der Anwendung notwendig ist.
AC-J-04 (zu F-11, F-12, GR-04) - Löschen eines referenzierten Kunden verweigern
Vorbedingung: Ein Kunde ist gespeichert und wird in mindestens einem Beleg referenziert.
Aktion: Die Anwender:in versucht, diesen Kunden zu löschen.
Erwartet: Der Löschvorgang wird verweigert. Das System gibt eine verständliche Fehlermeldung aus, die den Grund der Ablehnung klar benennt. Der Kundendatensatz bleibt vollständig erhalten.
AC-J-05 (zu F-11) - Löschen eines nicht referenzierten Kunden
Vorbedingung: Ein Kunde ist gespeichert und wird in keinem Beleg referenziert.
Aktion: Die Anwender:in löscht diesen Kunden.
Erwartet: Der Datensatz wird vollständig aus der Kundenliste entfernt und ist auch über den KundenService nicht mehr abrufbar.
AC-J-06 (zu F-08, F-09) - Suche nach Kunden
Vorbedingung: Mindestens 3 Kundendatensätze mit unterschiedlichen Namen sind gespeichert.
Aktion: Die Anwender:in gibt einen Namensbestandteil in das Suchfeld ein.
Erwartet: Nur die Kunden, deren Name den Suchbegriff enthält, werden in der Ergebnisliste angezeigt. Kunden ohne Übereinstimmung werden nicht angezeigt.
AC-J-07 (zu F-10) - Interner Zugriff über KundenService
Vorbedingung: Ein Kunde mit der ID „K-000001" ist gespeichert.
Aktion: Eine andere Komponente ruft findeKunde("K-000001") über das KundenService-Interface auf.
Erwartet: Das System gibt den vollständigen Kundendatensatz zurück. Bei einer nicht existierenden ID wird null zurückgegeben.
AC-J-08 (zu NF-SEC-01, Q-02) - Datenpersistenz nach Neustart
Vorbedingung: Mehrere Kundendatensätze sind gespeichert.
Aktion: Die Anwendung wird vollständig geschlossen und anschließend neu gestartet.
Erwartet: Alle zuvor gespeicherten Kundendatensätze stehen nach dem Neustart vollständig und unverändert zur Verfügung.
AC-J-09 (zu F-13) - Transaktionshistorie je Kunde
Vorbedingung: Ein Kunde ist in mindestens zwei Belegen referenziert.
Aktion: Die Anwender:in öffnet den Kundendatensatz und ruft die Transaktionshistorie ab.
Erwartet: Alle verknüpften Belegnummern werden vollständig und korrekt angezeigt.
AC-J-10 (zu NF-USE-01, Q-01) - Usability-Test Kunde anlegen
Vorbedingung: Eine Testperson ohne Vorkenntnisse öffnet die Anwendung zum ersten Mal.
Aktion: Die Testperson legt ohne externe Hilfe einen vollständigen Kundendatensatz an.
Erwartet: Der Vorgang wird in unter 3 Minuten erfolgreich abgeschlossen. Der Test wird mit mindestens 5 Testpersonen wiederholt, von denen alle das Ziel innerhalb der Zeitvorgabe erreichen.
# Traceability LH &lt;-&gt; PH
Jede für Gruppe J relevanten Lastenheft-Anforderungen ist mindestens einer Pflichtenheft-Anforderung zugeordnet. Damit wird sichergestellt, dass keine Anforderung aus dem Lastenheft in der Umsetzung verloren geht.
| LH-Anforderung | Beschreibung (LH) | PH-Anforderung/en |
| -------------- | ------------------------------------------------------------------- | ---------------------- |
| BA-01 | Kunde anlegen mit Namen, Anschrift und Kontaktinformationen | F-01, F-02, F-03, F-04 |
| BA-01 | Kunde bearbeiten | F-05, F-06, F-07 |
| BA-01 | Kunden abrufen und suchen | F-08, F-09, F-10 |
| BA-01 | Kunden löschen unter Berücksichtigung referenzieller Integrität | F-11, F-12 |
| BA-01 | Transaktionshistorie je Kunde | F-13 |
| GR-04 | Löschsperre für referenzierte Kundendatensätze | F-12, NF-INT-01 |
| Q-01 | Benutzerfreundlichkeit: Kernprozesse ohne Einarbeitung bedienbar | NF-USE-01, NF-USE-02 |
| Q-02 | Datensicherheit und Zuverlässigkeit: Persistente lokale Speicherung | NF-SEC-01, NF-SEC-02 |
| Q-03 | Revisionssicherheit: Schutz referenzierter Datensätze vor Löschung | NF-INT-01 |
Hinweis zur Abgrenzung
Die Geschäftsregeln GR-01 bis GR-03 sowie GR-05 und GR-06 betreffen ausschließlich die Beleglogik und liegen damit in der Verantwortung von Gruppe K. Die Komponente J stellt Kundendaten lediglich lesend über den KundenService bereit und ist von diesen Regeln zwar indirekt betroffen, spezifiziert sie jedoch nicht.
Die funktionalen Anforderungen BA-02 bis BA-04 aus dem Lastenheft werden von den Gruppen L (Bestandsmanagement) und K (Belegworkflow) abgedeckt und sind nicht Gegenstand dieses Dokuments.
# Modultestplan
Die folgenden Testfälle sind deterministisch formuliert, das bedeutet sie haben feste Eingaben und klar definierte erwartete Ausgaben. Alle Testfälle sind direkt mit JUnit 5 umsetzbar. String-Vergleiche werden mit assertEquals, Nullprüfungen mit assertNull und Ausnahmen mit assertThrows getestet
| TC-ID | Abgedeckte PH-Anf. | Vorbedingung | Test-Eingabe (Aktion) | Erwartetes Ergebnis |
| ----- | ------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| TC-01 | F-01, F-02 | Keine Kunde vorhanden, KundenIdGenerator bei Startwert | Vollständiger Kundendatensatz mit allen Pflichtfeldern | Kunde wird gespeichert, vergebene ID lautet „K-000001" |
| TC-02 | F-02 | Letzter vergebener Zähler = 5 | naechsteKundenId() aufrufen | Rückgabe „K-000006" (führende Nullen, String-Format) |
| TC-03 | F-03, NF-USE-02 | Formular geöffnet, kein Kunde vorhanden | Kundendatensatz ohne E-Mail-Adresse speichern | Speichervorgang wird abgelehnt, Validierungsfehler benennt „E-Mail" als fehlendes Pflichtfeld |
| TC-04 | F-03 | Formular geöffnet, kein Kunde vorhanden | Kundendatensatz ohne Name speichern | Speichervorgang wird abgelehnt, Validierungsfehler benennt „Name" als fehlendes Pflichtfeld |
| TC-05 | F-04, NF-SEC-01 | Kunde „K-000001" wurde gespeichert | Anwendung schließen und neu starten, dann findenKunden"K-000001") aufrufen | Datensatz ist vollständig und unverändert verfügbar |
| TC-06 | F-05, F-06, F-07 | Kunde „K-000001" mit E-Mail [alt@mail.de](mailto:alt@mail.de) ist gepseichert | E-Mail auf [neu@mail.de](mailto:neu@mail.de) ändern und speichern | findenKunde(„K-000001") gibt Datensatz mit E-Mail [neu@mail.de](mailto:neu@mail.de) zurück, ohne Neustart der Anwendung |
| TC-07 | F-09 | Drei Kunden gespeichert: „Khazanovych GmbH", „Winkler AG", „Senger KG" | sucheNachName ("Kh") aufrufen | Rückgabe enthält ausschließlich „Khazanovych GmbH", die anderen beiden Einträge sind nicht enthalten |
| TC-08 | F-10 | Kunde „K-000001" ist gespeichert | findeKunde("K-000001") über KundenService aufrufen | Vollständiger Kundendatensatz wird zurückgegeben |
| TC-09 | F-10 | Kein Kunde mit ID „K-999999" vorhanden | findeKunde("K-999999") über KundenService aufrufen | Rückgabe ist null |
| TC-10 | F-11, F-12, GR-04 | Kunde „K-000001" ist in Beleg „R-2026-000001" referenziert | kundeLoschen("K-000001") aufrufen | Löschvorgang wird mit einer IllegalStateException abgelehnt, Datensatz bleibt vollständig erhalten |
| TC-11 | F-11 | Kunden „K-000002" ist in keinem Beleg referenziert | kundeLoschen("K-000002") aufrufen | Datensatz wird gelöscht, findeKunde("K-000002") gibt anschließend null zurück |
| TC-12 | F-13 | Kunde „K-000001" ist in den Belegen „AN-2026-000001" und „R-2026-000001" referenziert | Transaktionshistorie für „K-000001" abrufen | Rückgabe enthält genau die Belegnummern „AN-2026-000001" und „R-2026-000001", keine weiteren Einträge |
| TC-13 | NF-PERF-01 | 5.000 Kundendatensätze sind gespeichert | sucheNachname ("Khazanovych") aufrufen | Ergebnis wird in unter 1 Sekunde zurückgegeben |
Damit sind 13 Testfälle spezifiziert, die alle funktionalen Kernregeln (F-01 bis F-13) sowie die relevanten Geschäftsregeln (GR-04) und Qualitätsanforderungen (NF-SEC-01, NF-USE-02, NF-PERF-01) abdecken
# Anhänge
## 11.1 Abkürzungen
| Abkürzung | Bedeutung |
| --------- | ------------------------------------------------------------------------------------------ |
| F | Funktionale Anforderung |
| NF | Nicht-funktionale Anforderung |
| IF | Schnittstelle (Interface) |
| AC | Abnahmekriterium (Acceptance Criterion) |
| TC | Testfall (Test Case) |
| BA | Benutzeranforderung |
| GR | Geschäftsregel |
| Q | Qualitätsanforderung |
| GUI | Graphical User Interface (Grafische Benutzeroberfläche) |
| DSGVO | Datenschutz-Grundverordnung der Europäischen Union |
| GoBD | Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern in elektronischer Form |
| UstG | Umsatzsteuergesetz |
| Ust-IdNr | Umsatzsteuer-Identifikationsnummer |
## 11.2 Glossar
Es gilt das Glossar des Lastenhefts (Abschnitt 8.2) unverändert. Nachfolgend sind ausschließlich Begriffe aufgeführt, die spezifisch für dieses Pflichtenheft eingeführt wurden und im Lastenheft nicht enthalten sind

View File

@ -0,0 +1,451 @@
Software Engineering 1 | Pflichtenheft | Team 3 Gruppe K |
Belegworkflow
Datum: 15.06.2026 | Version: 1.4
Weiterleitung zum Git: <https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3>
Inhalt
1\. Einleitung und Zielbestimmung 2
1.1 Ziel des Dokuments 2
1.2 Geltungsbereich 2
1.3 Definitionen und Abkürzungen 3
1.4 Referenzen 3
2\. Systemüberblick 3
2.1 Kurzüberblick 3
2.2 Abgrenzung 3
2.3 Grobe Systemfunktionen 4
2.4 UML-Bezug 4
3\. Stakeholder und Kontext 4
4\. Funktionale Anforderungen 4
4.1 Angebot erstellen 5
4.2 Angebot in Rechnung überführen 5
4.3 Rechnung und Rechtskonformität 5
4.4 Übergreifende Belegregeln 5
5\. Nicht-funktionale Anforderungen 6
5.1 Benutzbarkeit 6
5.2 Datensicherheit und Zuverlässigkeit 6
5.3 Revisionssicherheit 6
6\. Daten und Schnittstellen 6
6.1 Designgrundsätze für Datentypen 6
6.2 Datenobjekte 6
6.3 Interne Schnittstellen 9
6.4 Externe Schnittstellen 9
7\. Systemarchitektur (logisch, grob) 10
7.1 Klassendiagramm 10
7.2 Sequenzdiagramm 11
8\. Testbare Abnahmekriterien 11
9\. Traceability: Lastenheft (LH) - Pflichtenheft (PH) 13
10\. Modultestplan 14
11\. Anhänge 16
11.1 Abkürzungen 16
11.2 Begriffe 16
Freigabeübersicht
| Autor | Freigebenden | Prüfer |
| ------------------------------------- | ------------------------------------- | ----------------------- |
| Taha Erdogan, Kutlu Patir, Sarav Guli | Taha Erdogan, Kutlu Patir, Sarav Guli | Prof. Dr. Marmitt, Gerd |
| Entwickler | Entwickler | Modulverantwortlicher |
| 15.06.2026 | 15.06.2026 | Datum, Unterschrift |
Dokumentenhistorie
| Version | Datum | Autor | Grund der Änderung |
| ------- | ---------- | ------------------------------------- | ------------------------ |
| 1.0 | 10.06.2026 | Taha Erdogan, Kutlu Patir, Sarav Guli | Grundstruktur |
| 1.1 | 11.06.2026 | Taha Erdogan, Kutlu Patir, Sarav Guli | Anforderungen |
| 1.2 | 12.06.2026 | Taha Erdogan, Kutlu Patir, Sarav Guli | Daten und Schnittstellen |
| 1.3 | 13.06.2026 | Taha Erdogan, Kutlu Patir, Sarav Guli | UML und Tests |
| 1.4 | 15.06.2026 | Taha Erdogan, Kutlu Patir, Sarav Guli | Finale Überarbeitung |
# 1\. Einleitung und Zielbestimmung
## 1.1 Ziel des Dokuments
Ziel dieses Pflichtenhefts ist die vollständige und testbare Spezifikation der Komponente Belegworkflow. Die Komponente beschreibt die systemgestützte Erstellung von Angeboten sowie die Überführung eines vorhandenen Angebots in eine rechtskonforme Rechnung gemäß § 14 UStG.
Das Dokument konkretisiert die Anforderungen des Lastenhefts aus Sicht des Auftragnehmers. Es dient als verbindliche Grundlage für Implementierung, Schnittstellenabstimmung und Komponententest der Belegworkflow-Komponente.
## 1.2 Geltungsbereich
Dieses Dokument gilt für die Komponente Gruppe K - Belegworkflow. Die Gesamtanwendung wird arbeitsteilig in mehrere Kernbereiche spezifiziert. Dieses Pflichtenheft beschreibt ausschließlich die Fachlogik, Datenhaltung und Schnittstellen des Belegworkflows.
Die Komponente K kapselt die Erstellung und Speicherung von Angeboten, die Übernahme von Angebotsdaten in Rechnungen, die automatische Summenberechnung, die Vergabe lückenloser Rechnungsnummern, den Schreibschutz finaler Belege sowie den PDF-Export von Angeboten und Rechnungen.
Nicht Bestandteil dieses Dokuments sind die Pflege von Kundenstammdaten, die Pflege von Produkt- und Bestandsdaten sowie die grafische Benutzeroberfläche. Diese Bereiche werden über definierte Schnittstellen angebunden und hier nur soweit beschrieben, wie sie für den Belegworkflow erforderlich sind.
## 1.3 Definitionen und Abkürzungen
Fachbegriffe wie Fakturierung, Angebot, Rechnung, Snapshot-Prinzip, GoBD und DSGVO werden im Lastenheft Team 3 definiert und gelten für dieses Dokument unverändert. Dokumentspezifische Abkürzungen sind in Kapitel 11 aufgeführt.
## 1.4 Referenzen
\- Lastenheft "Fakturierungssystem", Team 3, Version 1.0, 15.05.2026
\- § 14 UStG - Pflichtangaben einer Rechnung
\- GoBD - Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern, Aufzeichnungen und Unterlagen in elektronischer Form
\- DSGVO - Datenschutz-Grundverordnung der Europäischen Union
\- Vorlesungsunterlagen Software Engineering 1 (SoSe 2026), Foliensatz "Lasten- und Pflichtenheft"
# 2\. Systemüberblick
## 2.1 Kurzüberblick
Die Anwendung ist eine Einzelplatz-Desktop-Anwendung mit lokaler Datenhaltung. Die Komponente Belegworkflow stellt die Fachlogik für kaufmännische Belege bereit. Sie besitzt keine eigene Oberfläche, sondern wird durch die Benutzeroberfläche aufgerufen und nutzt Kunden- sowie Produktdaten über Schnittstellen.
Im Mittelpunkt stehen zwei Vorgänge: Erstens kann ein Angebot für einen vorhandenen Kunden mit mindestens einer Produktposition erstellt werden. Zweitens kann aus einem vorhandenen Angebot eine Rechnung erzeugt werden, wobei Kundendaten, Positionen und Mengen übernommen werden und eine eindeutige Rechnungsnummer vergeben wird.
## 2.2 Abgrenzung
Im Umfang dieser Komponente:
\- Erstellung und Speicherung von Angeboten mit Kunde, Positionen, Summen und Status.
\- Überführung eines vorhandenen Angebots in eine Rechnung.
\- Automatische Berechnung von Netto-, Umsatzsteuer- und Bruttosumme.
\- Snapshot-Speicherung von Produktbezeichnung, Einzelpreis und Steuersatz in Dokumentpositionen.
\- Vergabe lückenloser und fortlaufender Rechnungsnummern.
\- Schreibschutz für versendete bzw. festgeschriebene Belege.
\- PDF-Export von Angebot und Rechnung in das lokale Dateisystem.
\- Bereitstellung von Referenzprüfungen für Kunden und Produkte.
Nicht im Umfang dieser Komponente:
\- Anlegen, Ändern oder Löschen von Kundendaten.
\- Anlegen, Ändern oder Löschen von Produkt- und Bestandsdaten.
\- Gestaltung und technische Umsetzung der grafischen Benutzeroberfläche.
\- Cloud-Speicherung, Mehrbenutzerbetrieb oder externe Buchhaltungsschnittstellen.
\- Aktive Spezifikation von Auftragsbestätigung und Lieferschein, da der für Gruppe K relevante Lastenheft-Workflow direkt Angebot zu Rechnung beschreibt.
## 2.3 Grobe Systemfunktionen
Aufruf durch Benutzeroberfläche -> Abruf von Kunden- und Produktdaten -> Validierung der Pflichtdaten -> Erstellung des Angebots -> Berechnung der Summen -> Speicherung im lokalen Datenbestand -> spätere Auswahl des Angebots -> Erzeugung der Rechnung mit Datenübernahme -> Vergabe der Rechnungsnummer -> Speicherung und optionaler PDF-Export.
## 2.4 UML-Bezug
Die logische Architektur der Komponente wird in Kapitel 7 dargestellt. Das Klassendiagramm beschreibt die fachlichen Klassen, Services und Schnittstellen des Belegworkflows. Das Sequenzdiagramm beschreibt den Ablauf, bei dem aus einem vorhandenen Angebot eine Rechnung erzeugt und anschließend als PDF exportiert wird.
# 3\. Stakeholder und Kontext
Stakeholder, Benutzerrollen und Systemkontext sind im Lastenheft Team 3 definiert und gelten für dieses Pflichtenheft grundsätzlich unverändert.
Maßgeblicher Akteur ist der Einzelanwender bzw. die Einzelanwenderin. Diese Person nutzt die Anwendung, um Angebote zu erstellen und aus bestehenden Angeboten Rechnungen zu erzeugen.
Angrenzende Komponenten und Systeme sind die grafische Benutzeroberfläche, die Kundenverwaltung, die Produkt- und Bestandsverwaltung, das lokale Dateisystem sowie die Druck- und PDF-Exportfunktion. Die Kommunikation erfolgt über fachlich definierte Schnittstellen, nicht über direkte Datenbankzugriffe der Oberfläche.
# 4\. Funktionale Anforderungen
Die folgenden Anforderungen leiten sich direkt aus den Lastenheft-Anforderungen BA-03, BA-04 sowie den Geschäftsregeln GR-01 bis GR-06 ab. Nicht benötigte Funktionen wurden nicht zusätzlich spezifiziert, um den Belegworkflow nicht unnötig zu überladen.
## 4.1 Angebot erstellen
F-01: Das System MUSS es ermöglichen, ein Angebot für einen vorhandenen Kunden mit mindestens einer Produktposition zu erstellen.
F-02: WENN ein Angebot gespeichert wird, DANN MUSS das System eine eindeutige Angebotsnummer, das Erstellungsdatum, ein Gültigkeitsdatum und den Status OFFEN setzen.
F-03: Das System MUSS für jedes Angebot die Nettosumme, Umsatzsteuersumme und Bruttosumme automatisch aus den Dokumentpositionen berechnen.
F-04: WENN eine Produktposition in ein Angebot übernommen wird, DANN MUSS das System Produktbezeichnung, Netto-Einzelpreis und Steuersatz als Snapshot in der Dokumentposition speichern.
## 4.2 Angebot in Rechnung überführen
F-05: WENN aus einem Angebot eine Rechnung erzeugt wird, DANN MUSS das System den Status des Angebots auf UEBERFUEHRT setzen und eine Rückreferenz zwischen Rechnung und Angebot speichern.
F-06: Das System MUSS es ermöglichen, aus einem vorhandenen Angebot eine Rechnung zu erzeugen, wobei Kundendaten, Positionen und Mengen unverändert aus dem Angebot übernommen werden.
F-07: WENN eine Rechnung erzeugt wird, DANN MUSS das System automatisch die nächste freie, fortlaufende und lückenlose Rechnungsnummer vergeben.
## 4.3 Rechnung und Rechtskonformität
F-08: Das System MUSS in jeder Rechnung die im Lastenheft geforderten Rechnungsdaten führen: Rechnungsnummer, Rechnungsdatum, Leistungsdatum, Kundenreferenz, Angebotsreferenz, Positionen, Netto-/Steuer-/Bruttosumme, Zahlungsziel, Status und die Pflichtangaben gemäß § 14 UStG.
F-09: Das System MUSS für jede Rechnung Netto-, Umsatzsteuer- und Bruttosumme automatisch berechnen und als BigDecimal-Werte speichern.
F-10: WENN eine Rechnung versendet oder festgeschrieben wurde, DANN MUSS das System jede inhaltliche Änderung an Rechnungsnummer, Kundendaten, Positionen und Beträgen verweigern.
## 4.4 Übergreifende Belegregeln
F-11: Das System MUSS es ermöglichen, gespeicherte Angebote und Rechnungen als druckbare PDF-Dateien in das lokale Dateisystem zu exportieren.
F-12: Das System MUSS über Serviceoperationen prüfen können, ob ein Kunde oder Produkt in einem aktiven oder archivierten Beleg referenziert wird, damit Löschsperren gemäß GR-04 unterstützt werden.
# 5\. Nicht-funktionale Anforderungen
Die nicht-funktionalen Anforderungen werden mit den IDs NF-01 bis NF-04 geführt. Die Benennung ist bewusst einfach gehalten und leitet sich aus den Qualitätsanforderungen Q-01 bis Q-03 des Lastenhefts ab.
## 5.1 Benutzbarkeit
NF-01: Die Erstellung eines Angebots und die anschließende Erzeugung einer Rechnung aus diesem Angebot MUSS durch eine erstmalige Anwender:in ohne externe Hilfe innerhalb der im Lastenheft geforderten Kernprozess-Zeit von maximal 10 Minuten durchführbar sein.
## 5.2 Datensicherheit und Zuverlässigkeit
NF-02: Das System MUSS gespeicherte Angebote, Rechnungen und Dokumentpositionen dauerhaft lokal speichern, sodass sie nach einem Neustart der Anwendung unverändert verfügbar sind.
NF-03: Das System MUSS Belegdaten ausschließlich lokal verarbeiten und speichern. Eine automatische Übertragung von Kunden-, Produkt- oder Belegdaten an externe Dienste ist nicht zulässig.
## 5.3 Revisionssicherheit
NF-04: Das System MUSS finalisierte Rechnungen systemseitig vor nachträglicher Manipulation schützen. Der Schreibschutz ist auf Datenmodell- und Serviceebene durchzusetzen und darf nicht allein von der Benutzeroberfläche abhängen.
# 6\. Daten und Schnittstellen
## 6.1 Designgrundsätze für Datentypen
\- Geldbeträge werden als java.math.BigDecimal mit Scale 2 und kaufmännischer Rundung RoundingMode.HALF_UP geführt. double und float sind für Geldbeträge unzulässig.
\- Steuersätze werden als java.math.BigDecimal geführt, z. B. 0.19 oder 0.07.
\- IDs und Belegnummern werden als String geführt, da sie Präfixe und führende Nullen enthalten können.
\- Datumswerte werden als java.time.LocalDate geführt.
\- Mengen und Bestände werden als int geführt.
\- Positionslisten werden als List(Dokumentposition) geführt.
## 6.2 Datenobjekte
### 6.2.1 Schnittstellenobjekt: Kundendaten
| Attribut | Java-Datentyp | Beschreibung / Validierung |
| ----------------- | ------------- | ------------------------------------------------------------ |
| kundenId | String | Eindeutige Kunden-ID, Referenz auf vorhandenen Kunden. |
| nameFirmenname | String | Name oder Firmenbezeichnung; für Belege erforderlich. |
| anschrift | String | Vollständige Anschrift; für Rechnungslegung erforderlich. |
| steuernummerUStId | String | Steuernummer oder USt-IdNr.; sofern im Kundenstamm gepflegt. |
| eMail | String | Optionale E-Mail-Adresse. |
| telefon | String | Optionale Telefonnummer. |
### 6.2.2 Schnittstellenobjekt: Produktdaten
| Attribut | Java-Datentyp | Beschreibung / Validierung |
| ------------------ | ------------- | --------------------------------------------------------------------- |
| produktId | String | Eindeutige Produkt-ID, Referenz auf vorhandenes Produkt. |
| bezeichnung | String | Produktbezeichnung, wird als Snapshot in Dokumentposition übernommen. |
| nettoEinzelpreis | BigDecimal | Netto-Einzelpreis mit Scale 2. |
| mehrwertsteuersatz | BigDecimal | Mehrwertsteuersatz als Faktor, z. B. 0.19. |
| bestand | int | Aktueller Bestand; wird im Belegworkflow nur lesend berücksichtigt. |
| beschreibung | String | Optionale Produktbeschreibung. |
### 6.2.3 Klasse: Dokumentposition
| Attribut | Java-Datentyp | Beschreibung / Validierung |
| ------------------- | ------------- | --------------------------------------------------------- |
| produktReferenz | String | Referenz auf produktId. |
| bezeichnung | String | Snapshot der Produktbezeichnung zum Erstellungszeitpunkt. |
| menge | int | Menge der Position; muss größer als 0 sein. |
| einzelpreisNetto | BigDecimal | Snapshot des Netto-Einzelpreises, Scale 2. |
| steuersatz | BigDecimal | Snapshot des Mehrwertsteuersatzes als Faktor. |
| positionssummeNetto | BigDecimal | Berechnetes Attribut: einzelpreisNetto × menge, Scale 2. |
### 6.2.4 Abstrakte Basisklasse: Dokument
| Attribut | Java-Datentyp | Beschreibung / Validierung |
| --------------- | ---------------------- | ---------------------------------------------------------------- |
| belegnummer | String | Eindeutige Nummer des Belegs. |
| datum | LocalDate | Erstellungsdatum. |
| kundenReferenz | String | Referenz auf kundenId. |
| positionen | List(Dokumentposition) | Mindestens eine Position. |
| summeNetto | BigDecimal | Summe aller Positionssummen, Scale 2. |
| summeSteuer | BigDecimal | Summe der Steuerbeträge, Scale 2. |
| summeBrutto | BigDecimal | summeNetto + summeSteuer, Scale 2. |
| festgeschrieben | boolean | true, wenn der Beleg nicht mehr inhaltlich geändert werden darf. |
### 6.2.5 Konkrete Spezialisierungen
| Klasse | Zusätzliche Attribute (Java-Typ) | Beschreibung |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Angebot | gueltigBis: LocalDate <br>status: AngebotStatus | Angebot mit Bindefrist. Statuswerte: OFFEN, UEBERFUEHRT, VERWORFEN. |
| Rechnung | rechnungsdatum: LocalDate <br>leistungsdatum: LocalDate <br>zahlungsziel: LocalDate <br>angebotsNr: String <br>status: RechnungStatus | Rechnung aus Angebot. Statuswerte: OFFEN, BEZAHLT. Der Schreibschutz wird zusätzlich über festgeschrieben abgebildet. |
## 6.3 Interne Schnittstellen
Die Schnittstellen sind Java-nah formuliert, damit sie als direkter Input für den Komponententest genutzt werden können. Sie beschreiben keine konkrete Persistenztechnologie.
public interface KundenService {
Optional(Kundendaten) findeKunde(String kundenId);
}
<br/>public interface ProduktService {
Optional(Produktdaten) findeProdukt(String produktId);
}
<br/>public interface DokumentService {
Angebot erstelleAngebot(String kundenId, List&lt;PositionEingabe&gt; positionen, LocalDate gueltigBis);
Rechnung erstelleRechnungAusAngebot(String angebotsNr, LocalDate rechnungsdatum,
LocalDate leistungsdatum, LocalDate zahlungsziel);
void finalisiereRechnung(String rechnungsNr);
Path exportierePdf(String belegNr, Path zielDatei) throws IOException;
boolean istKundeReferenziert(String kundenId);
boolean istProduktReferenziert(String produktId);
}
<br/>public interface BelegnummernGenerator {
String naechsteAngebotsnummer(int jahr);
String naechsteRechnungsnummer(int jahr);
}
<br/>public interface PdfExporter {
void exportiere(Dokument dokument, Path zielDatei) throws IOException;
}
## 6.4 Externe Schnittstellen
Die ID-Bezeichnungen IF-01 bis IF-03 werden aus dem Lastenheft übernommen. Sie werden nicht neu nummeriert, damit die Traceability zwischen Lastenheft und Pflichtenheft eindeutig bleibt.
| ID | Schnittstelle | Zweck und technischer Rahmen |
| ----- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| IF-01 | Benutzerschnittstelle (GUI) | Ruft die Serviceoperationen des Belegworkflows auf. Die konkrete Gestaltung der Oberfläche ist nicht Teil dieses Dokuments. |
| IF-02 | Lokales Dateisystem | Persistente Speicherung der Belege und Ablage exportierter PDF-Dateien. Der Zugriff erfolgt z. B. über java.nio.file.Path. |
| IF-03 | Druck-/Export-Schnittstelle | Erzeugung druckbarer PDF-Dokumente für Angebote und Rechnungen. |
# 7\. Systemarchitektur (logisch, grob)
Die Komponente folgt einer einfachen schichtenbasierten Architektur. Die Benutzeroberfläche ruft den DokumentService auf. Dieser enthält die fachliche Logik, validiert Eingaben, ruft Kunden- und Produktdaten über Schnittstellen ab, berechnet Summen, fordert Belegnummern an und delegiert Speicherung sowie PDF-Export an technische Hilfskomponenten.
## 7.1 Klassendiagramm
_Abbildung 1: UML-Klassendiagramm der Komponente Belegworkflow_
_Abbildung 1 zeigt die zentralen Klassen und Schnittstellen der Komponente. Der DokumentService bildet den fachlichen Einstiegspunkt. Angebot und Rechnung erben gemeinsame Attribute aus der abstrakten Klasse Dokument. Dokumentposition speichert Produktdaten als Snapshot, sodass spätere Produktänderungen bestehende Belege nicht verändern._
## 7.2 Sequenzdiagramm
_Abbildung 2: UML-Sequenzdiagramm "Rechnung aus Angebot erzeugen"_
_Abbildung 2 beschreibt den Hauptablauf aus BA-04. Die Benutzeroberfläche fordert die Rechnungserzeugung an. Der DokumentService lädt das Angebot, prüft den Status, übernimmt die Angebotsdaten, fordert eine neue Rechnungsnummer an, speichert die Rechnung und setzt das Angebot auf UEBERFUEHRT. Der PDF-Export erfolgt danach als eigener Schritt._
# 8\. Testbare Abnahmekriterien
Die folgenden Abnahmekriterien verwenden die ID AC-01 bis AC-07. AC steht für Abnahmekriterium. Ein zusätzlicher Präfix ist nicht erforderlich, da dieses Dokument nur die Komponente Belegworkflow beschreibt.
| ID | Bezug | Vorbedingung | Aktion | Erwartetes Ergebnis |
| ----- | ----------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| AC-01 | F-01, F-02, BA-03 | Ein Kunde und mindestens ein Produkt existieren. | Ein Angebot mit einer Produktposition wird erstellt. | Das Angebot wird mit Angebotsnummer, Datum, Gültigkeitsdatum, Positionen und Status OFFEN gespeichert. |
| AC-02 | F-03, GR-06 | Ein Angebot enthält mehrere Positionen. | Das Angebot wird gespeichert. | Netto-, Steuer- und Bruttosumme werden korrekt berechnet und gespeichert. |
| AC-03 | F-04, GR-03 | Ein Produkt besitzt Preis 100.00 und Steuersatz 0.19. | Produkt wird in ein Angebot übernommen und danach der Produktpreis extern geändert. | Die Dokumentposition behält den ursprünglichen Preis und Steuersatz als Snapshot. |
| AC-04 | F-06, BA-04 | Ein gespeichertes Angebot im Status OFFEN existiert. | Aus dem Angebot wird eine Rechnung erzeugt. | Die Rechnung übernimmt Kunde, Positionen und Mengen und speichert die Angebotsreferenz. |
| AC-05 | F-07, GR-01 | Die letzte Rechnungsnummer eines Jahres ist bekannt. | Eine neue Rechnung wird erzeugt. | Das System vergibt die nächste freie, lückenlose Rechnungsnummer und verwendet keine alte Nummer erneut. |
| AC-06 | F-08, F-10, Q-03 | Eine Rechnung wurde erzeugt. | Die Rechnung wird finalisiert; danach wird eine Positionsänderung versucht. | Die Rechnung enthält die Pflichtdaten und lehnt die nachträgliche Änderung ab. |
| AC-07 | F-11, IF-03 | Ein Angebot oder eine Rechnung ist gespeichert. | Der Beleg wird als PDF exportiert. | Eine druckbare PDF-Datei wird im lokalen Dateisystem erzeugt. |
# 9\. Traceability: Lastenheft (LH) - Pflichtenheft (PH)
Die Traceability ordnet die für den Belegworkflow relevanten Lastenheft-Anforderungen den Anforderungen und Kapiteln dieses Pflichtenhefts zu. Anforderungen außerhalb des direkten Belegworkflow-Scopes werden nur über Schnittstellen berücksichtigt.
| LH-ID | Beschreibung aus dem Lastenheft | Zuordnung im Pflichtenheft |
| ----- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| BA-01 | Kundendatensätze anlegen und für Angebote/Rechnungen verwenden. | Nicht direkte Umsetzung; Verwendung über Kundendaten/KundenService in Kapitel 6.2.1 und 6.3. |
| BA-02 | Produktdaten mit Bezeichnung, Preis und Bestand speichern und in Dokumenten verwenden. | Nicht direkte Umsetzung; Verwendung über Produktdaten/ProduktService in Kapitel 6.2.2 und F-04. |
| BA-03 | Angebot für vorhandenen Kunden mit mindestens einer Produktposition erstellen. | F-01 bis F-04, AC-01 bis AC-03. |
| BA-04 | Aus einem vorhandenen Angebot eine Rechnung erzeugen. | F-05 bis F-10, AC-04 bis AC-06. |
| Q-01 | Intuitive Bedienung der Kernprozesse innerhalb von 10 Minuten. | NF-01. |
| Q-02 | Datensicherheit und Zuverlässigkeit; Daten bleiben erhalten und geschützt. | NF-02, NF-03, IF-02. |
| Q-03 | Finale Rechnungen vor Manipulation schützen. | F-10, NF-04, AC-06. |
| GR-01 | Fortlaufende, lückenlose Rechnungsnummern. | F-07, BelegnummernGenerator, AC-05. |
| GR-02 | Unveränderlichkeit finaler Belege. | F-10, NF-04, AC-06. |
| GR-03 | Snapshot-Prinzip für Preis und Steuersatz. | F-04, Dokumentposition, AC-03. |
| GR-04 | Referenzielle Integrität für Kunden und Produkte. | F-12, DokumentService.istKundeReferenziert(), DokumentService.istProduktReferenziert(). |
| GR-05 | Dokumentenketten-Konsistenz bei Folgedokumenten. | F-05, F-06, Rechnung.angebotsNr, AC-04. |
| GR-06 | Automatische Summenberechnung. | F-03, F-09, AC-02. |
| IF-01 | Benutzerschnittstelle. | IF-01 in Kapitel 6.4; Serviceaufrufe über DokumentService. |
| IF-02 | Lokales Dateisystem. | IF-02 in Kapitel 6.4; DokumentRepository/Persistenz. |
| IF-03 | Druck-/Export-Schnittstelle. | F-11, IF-03, PdfExporter, AC-07. |
| AK-05 | Aus bestehendem Angebot wird eine rechtskonforme Rechnung erzeugt. | AC-04, AC-05, AC-06. |
# 10\. Modultestplan
Die folgenden 10 Testfälle sind so formuliert, dass sie mit JUnit und Mocks für KundenService, ProduktService und PdfExporter umgesetzt werden können.
| TC | Abgedeckte PH-Anforderung | Vorbedingung | Eingabe | Erwartetes Ergebnis |
| ------- | ------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| TC-K-01 | F-01, F-02 | Kunde K-1 und Produkt P-1 existieren. | erstelleAngebot(K-1, Position P-1 Menge 2, gueltigBis) | Angebot wird gespeichert; Angebotsnummer, Datum, gueltigBis und Status OFFEN sind gesetzt. |
| TC-K-02 | F-03, F-09 | Positionen: 100.00 @ 0.19 und 50.00 @ 0.07. | Summenberechnung beim Speichern | summeNetto = 150.00, summeSteuer = 22.50, summeBrutto = 172.50. |
| TC-K-03 | F-04, GR-03 | Produkt P-1 hat Preis 100.00 und Steuersatz 0.19. | Produkt in Angebot übernehmen, danach Produktpreis im Mock ändern. | Dokumentposition behält einzelpreisNetto 100.00 und steuersatz 0.19. |
| TC-K-04 | F-06, GR-05 | Angebot AN-2026-000001 mit Kunde und 2 Positionen existiert. | erstelleRechnungAusAngebot(AN-2026-000001, ... ) | Rechnung enthält dieselben Kundendaten, Positionen und Mengen; angebotsNr ist gesetzt. |
| TC-K-05 | F-07, GR-01 | Letzte Rechnungsnummer ist R-2026-000009. | Neue Rechnung erstellen. | Neue Rechnungsnummer lautet R-2026-000010 und ist String. |
| TC-K-06 | F-05, GR-05 | Angebot ist OFFEN. | Rechnung aus Angebot erzeugen. | Angebotsstatus wird UEBERFUEHRT. |
| TC-K-07 | F-10, NF-04 | Rechnung existiert und wird finalisiert. | Nach finalisiereRechnung() wird eine Position geändert. | Änderung wird mit IllegalStateException verweigert. |
| TC-K-08 | F-08 | Rechnung wurde aus Angebot erzeugt. | Rechnung validieren. | Rechnungsnummer, Rechnungsdatum, Leistungsdatum, Kundenreferenz, Positionen, Summen, Zahlungsziel und Status sind vorhanden. |
| TC-K-09 | F-11, IF-03 | Gespeicherte Rechnung existiert; PdfExporter ist gemockt. | exportierePdf(rechnungsNr, zielDatei) | PdfExporter.exportiere(...) wird mit Rechnung und Path aufgerufen; Rückgabe enthält Zielpfad. |
| TC-K-10 | F-12, GR-04 | Repository enthält einen Beleg mit Produkt P-1 und Kunde K-1. | istProduktReferenziert(P-1), istKundeReferenziert(K-1) | Beide Aufrufe liefern true; unbekannte IDs liefern false. |
# 11\. Anhänge
## 11.1 Abkürzungen
| Abkürzung | Bedeutung |
| --------- | ------------------------------------------------------------------------------------------------------------------------- |
| F | Funktionale Anforderung |
| NF | Nicht-funktionale Anforderung |
| IF | Interface / Schnittstelle |
| AC | Abnahmekriterium |
| TC | Testfall |
| BA | Benutzeranforderung im Lastenheft |
| Q | Qualitätsanforderung |
| GR | Geschäftsregel |
| PH | Pflichtenheft |
| LH | Lastenheft |
| GUI | Graphical User Interface |
| UML | Unified Modeling Language |
| UStG | Umsatzsteuergesetz |
| GoBD | Grundsätze zur ordnungsmäßigen Führung und Aufbewahrung von Büchern, Aufzeichnungen und Unterlagen in elektronischer Form |
| DSGVO | Datenschutz-Grundverordnung |
## 11.2 Begriffe
| Begriff | Bedeutung |
| ---------------- | ------------------------------------------------------------------------------------------------------------- |
| Belegworkflow | Fachlicher Ablauf zur Erstellung von Angeboten und Rechnungen. |
| Angebot | Kaufmännisches Dokument mit Kunde, Positionen, Preisen und Summen vor Rechnungsstellung. |
| Rechnung | Dokument zur Zahlungsforderung gegenüber dem Kunden mit Pflichtangaben gemäß § 14 UStG. |
| Dokumentposition | Einzelne Belegzeile mit Produktreferenz, Menge, Preis, Steuersatz und Positionssumme. |
| Snapshot-Prinzip | Preis, Steuersatz und Bezeichnung werden beim Hinzufügen zur Position kopiert und bleiben später unverändert. |
| Festgeschrieben | Zustand, in dem ein Beleg nicht mehr inhaltlich verändert werden darf. |
| BigDecimal | Java-Datentyp für präzise Geldbeträge. |
| LocalDate | Java-Datentyp zur Speicherung eines Datums ohne Uhrzeit. |

View File

@ -0,0 +1,521 @@
Software Engineering 1 | Pflichtenheft Fakturierungssystem | Team 3 - Gruppe L |
Bestandsmanagement
Datum: 12.06.2026 | Version: 1.0
**INhaltsverzeichnis**
[1\. Einleitung und Zielbestimmung 1](#_Toc717003374)
[1.1 Ziel des Dokuments 1](#_Toc1632948684)
[1.2 Geltungsbereich 2](#_Toc772650685)
[1.3 Definitionen und Abkürzungen 2](#_Toc1215841514)
[1.4 Referenzen 2](#_Toc2014760614)
[2\. Systemüberblick 2](#_Toc1746248233)
[2.1 Kurzüberblick 3](#_Toc1360020926)
[2.2 Abgrenzung (Was gehört dazu/ was nicht) 3](#_Toc689315033)
[2.3 Grobe Systemfunktionen 3](#_Toc503028828)
[2.4 UML- Bezug 4](#_Toc417168666)
[3\. Stakeholder und Kontext 4](#_Toc2005610477)
[4\. Funktionale Anforderungen 4](#_Toc1552544159)
[4.1 Artikel anlegen und verwalten 5](#_Toc741449551)
[4.2 Artikel suchen und auswählen 5](#_Toc202346228)
[4.3 Lagerbestandsverwaltung 5](#_Toc1459730603)
[4.4 Löschen von Artikeln 5](#_Toc406714023)
[5\. Nicht-funktionale Anforderungen 5](#_Toc1647885970)
[5.1 Datensicherheit und Zuverlässigkeit 6](#_Toc257774846)
[5.2 Benutzbarkeit 6](#_Toc164272747)
[6\. Daten und Schnittstellen 6](#_Toc1569685136)
[6.1 Architektonische Designgrundsätze für Datentypen: 6](#_Toc848069818)
[6.2 Globale Status-Enumerations 6](#_Toc1493663702)
[6.2.1 Klasse: Stammdatenobjekte (Kunde und Produkt) 7](#_Toc1513100651)
[6.2.2 Klasse: Dokumentposition 8](#_Toc706574427)
[6.2.3 Abstrakte Basisklasse: Dokument 8](#_Toc647172810)
[6.2.4 Konkrete Spezialisierungen (Vererbung von Dokument) 9](#_Toc28079324)
[6.3 Interne Schnittstellen 9](#_Toc1582783355)
[6.4 Externe Schnittstellen 10](#_Toc1055024484)
[6.4.1 IF-Satzschablone (Spezifikation nach Standard) 11](#_Toc1055089710)
[7\. Systemarchitektur (logisch, grob) 11](#_Toc1618051390)
[7.1 Klassendiagramm 11](#_Toc1409170202)
[7.2 Sequenzdiagramm 12](#_Toc2005896104)
[8\. Testbare Abnahmekriterien 13](#_Toc1928042662)
[9\. Traceability: Lastenheft (LH) - Pflichtenheft (PH) 14](#_Toc1042167207)
[10\. Modultestplan 16](#_Toc119322171)
[11\. Anhänge 17](#_Toc133710866)
[11.1 Abkürzungen 17](#_Toc1562858566)
[11.2 Begriffe 17](#_Toc358783030)
**Freigabeübersicht**
| Autor | Freigebenden | Prüfer |
| ---------- | ------------ | ----------------------- |
| | | Prof. Dr. Marmitt, Gred |
| Entwickler | Entwickler | Modulverantwortlicher |
| 12.06.2026 | 12.06.2026 | Datum, Unterschrift |
**Dokumentenhistorie**
| Version | Datum | Autor | Grund der Änderung |
| ------- | ---------- | ------------------------------------------ | ------------------------------------------------- |
| 1.0 | 10.06.2026 | Feyza Yaz | Erstellung des Pflichtenhefts und Kapitel 1-3 |
| 1.1 | 10.06.2026 | Derin Abdullah, Melike Caliskan | Bearbeitung von Kapitel 4-7 |
| 1.2 | 11.06.2026 | Feyza Yaz | Bearbeitung von Kapitel 8 |
| 1.3 | 12.06.2026 | Melike Caliskan | Bearbeitung von Kapitel 9 |
| 1.4 | 12.06.2026 | Derin Abdullah | Bearbeitung von Kapitel 10 |
| 1.5 | 13.06.2026 | Derin Abdullah, Melike Caliskan, Feyza Yaz | Bearbeitung und Fertigstellung des Pflichtenhefts |
# 1\. Einleitung und Zielbestimmung
## 1.1 Ziel des Dokuments
Ziel dieses Pflichtenheft-Teils ist die vollständige und testbare Spezifikation der Bestandsverwaltung. Dies umfasst die persistente lokale Speicherung, Pflege und Bereitstellung von Produktdaten (Bezeichnung, Netto-Einzelpreis, Mehrwertsteuersatz, Bestand).
Zudem spezifiziert dieses Dokument die Durchsetzung von Geschäftsregeln auf Datenbank-/Logikebene, insbesondere die Gewährleistung der referenziellen Integrität (Löschsperren gemäß GR-04), sowie die Bereitstellung einer internen Schnittstelle, über die andere Systemkomponenten auf den Produktkatalog zugreifen können.
## 1.2 Geltungsbereich
Dieses Dokument gilt für die Komponente Gruppe L - Bestandsmanagement. Die Gesamtanwendung wird arbeitsteilig in vier Kernbereichen spezifiziert. Jede Arbeitsgruppe steuert einen eigenen Pflichtenheft-Teil bei, die das Gesamtsystem bilden:
| Gruppe | Komponente | Eigenes Pflichtenheft |
| ------ | ------------------ | --------------------- |
| J | Kundenverwaltung | separat |
| L | Bestandsmanagement | Dieses Dokument |
| K | Belegworkflow | separat |
| I | Benutzeroberfläche | separat |
Die Komponente L kapselt ausschließlich die Fachlogik und Datenhaltung für die Produkte (Anforderung BA-02). Sie enthält keine grafischen Oberflächen (GUI), sondern bietet definierte Services (Schnittstellen) an. Diese werden von der Benutzeroberfläche (für Such- und Listenansichten) und dem Belegworkflow (zur Übernahme von Preis-Snapshots in Dokumente gemäß GR-03) aufgerufen. Die Dialogführung und Darstellung der Produkte auf dem Bildschirm werden arbeitsteilig von der UI-Gruppe spezifiziert.
## 1.3 Definitionen und Abkürzungen
Fachbegriffe (z. B. Fakturierung, Stammdaten, Snapshot-Prinzip, Revisionssicherheit) sind im Glossar des Lastenhefts (§ 8.1 und § 8.2) definiert und gelten für dieses Dokument unverändert. Dokumentspezifische Abkürzungen siehe Kapitel 11.
## 1.4 Referenzen
- Lastenheft „Fakturierungssystem", Team 3, Version 1.0, 15.05.2026
- Pflichtenheft-Teil „Kundenverwaltung" (separat gepflegt)
- Pflichtenheft-Teil „Belegworkflow / Dokumentenzyklus" (separat gepflegt)
- Pflichtenheft-Teil „Programmoberfläche" (separat gepflegt)
- Vorlesungsunterlagen Software Engineering 1 (SoSe 2026), insb. Foliensatz „Lasten- und Pflichtenheft"
# 2\. Systemüberblick
## 2.1 Kurzüberblick
Die Anwendung ist eine Einzelplatz-Stand-Alone-Desktop-Anwendung mit lokaler Datenhaltung (keine Cloud, kein Server). Die Komponente **Bestandsmanagement** **(Gruppe L)** bildet das datentechnische Fundament für alle Artikel und Produkte des Systems. Sie stellt keine eigene grafische Benutzeroberfläche bereit, sondern kapselt ausschließlich die Fachlogik und die persistente Datenspeicherung der Produktdaten. Über definierte Service-Schnittstellen macht sie diese Funktionalität für die anderen Systemkomponenten (Benutzeroberfläche und Dokumentenzyklus) zugänglich.
## 2.2 Abgrenzung (Was gehört dazu/ was nicht)
Im Umfang dieser Komponente:
- Fachlogik und persistente lokale Speicherung der Produktdaten (Produkt-ID, Bezeichnung, Netto-Einzelpreis, Mehrwertsteuersatz, Bestand) gemäß BA-02.
- Fachliche Validierung beim Anlegen und Ändern von Produkten (z. B. Prüfung auf negative Bestände oder Preise).
- Durchsetzung der referenziellen Integrität (Löschsperre nach GR-04): Prüfung und Verweigerung von Löschvorgängen, falls ein Produkt bereits in aktiven oder archivierten Belegen referenziert wird.
- Bereitstellung einer internen Schnittstelle (ProduktService) für Such- und Lesezugriffe (für die GUI-Gruppe) sowie zur Bereitstellung der Preis- und Steuersatz-Snapshots für die Belegerstellung (GR-03).
Nicht im Umfang dieser Komponente:
- Grafische Benutzeroberfläche (GUI) für Formulare, Suchfelder oder Tabellenansichten der Produkte (Darstellung übernimmt die UI-Gruppe).
- Fachlogik und Persistenz der Kundenstammdaten (Kunden-Gruppe).
- Fachlogik des Belegworkflows, Summenberechnungen für Rechnungen und Dokumenten-Speicherung (Dokumenten-Gruppe).
- Netzwerkanbindungen, Cloud-Speicherung oder Mehrbenutzerverwaltung (gemäß Nicht-Zielen des Lastenhefts).
## 2.3 Grobe Systemfunktionen
Da diese Komponente als Service für andere Module fungiert, laufen die Systemfunktionen nachfolgendem Muster ab: Aufruf durch UI- oder Dokumenten-Komponente (z. B. "Produkt speichern" oder "Produkt suchen") → Durchführung der fachlichen Validierung und Integritätsprüfung (GR-04) → Ausführung der Datenbank-/Dateioperation → Rückgabe des Ergebnisses, des angeforderten Objekts oder Werfen einer Fehlermeldung an die aufrufende Komponente
## 2.4 UML- Bezug
Ein gemeinsames Use-Case-Diagramm aller Gruppen in der Einleitung des Gesamtdokuments gibt den Überblick über die Akteure und systemweiten Ziele. Die für Gruppe L relevanten und abgedeckten Use Cases sind fachlich: "Produkt anlegen", "Produkt bearbeiten", "Produkt suchen" und "Produkt löschen", sowie die systeminterne Beteiligung am Use Case "Beleg erstellen" (durch Bereitstellung der Artikeldaten). Die detaillierte logische Architektur und das Datenmodell (Klassendiagramm) dieser Komponente folgen in Kapitel 6 und 7.
# 3\. Stakeholder und Kontext
Stakeholder, Benutzerrollen und der Systemkontext sind im Lastenheft (Kapitel 2 und 3) definiert und gelten für dieses Pflichtenheft unverändert.
Für die Teil-Komponente L (Bestandsmanagement) gilt spezifisch:
**Maßgebliche Akteure:**
- **Einzelanwender: in** - natürliche Person (Selbstständige: r, Freiberufler: in, Kleinstunternehmer: in).
- _Hinweis für diese Komponente:_ Da das Bestandsmanagement ausschließlich die reine Fachlogik und Datenhaltung kapselt, interagiert die Anwender: in nicht direkt mit diesem Modul. Die Bedienung erfolgt indirekt über die Komponente Programmoberfläche (Gruppe D).
**Angrenzende Systeme/Komponenten:**
- **Intern - Programmoberfläche (Gruppe D):** Ruft die von uns definierten Dienste (ProduktService) auf, um Listen, Produktsuchen und Formulare zum Anlegen von Produkten grafisch darzustellen.
- **Intern - Prozess / Dokumentenzyklus (Gruppe A):** Fragt bei der Belegerstellung (z. B. Angebot oder Rechnung) die aktuellen Produktdaten und Steuersätze aus dem Bestandsmanagement ab, um diese gemäß dem Snapshot-Prinzip (GR-03) unveränderlich im jeweiligen Beleg zu fixieren.
- **Extern - Lokales Dateisystem (IF-02):** Die Komponente L greift zur Umsetzung von IF-02 direkt auf das lokale Dateisystem zu, um die Produktdaten (Bezeichnung, Preis, Bestand) persistent abzuspeichern, sodass die Datenschutzvorgaben (DSGVO) gewahrt bleiben und die Daten ohne Internetverbindung zur Verfügung stehen.
# 4\. Funktionale Anforderungen
## 4.1 Artikel anlegen und verwalten
**F-01:** Das System MUSS es der Anwender:in ERMÖGLICHEN, einen Artikel mit Artikelnummer, Bezeichnung, Netto-Einzelpreis, Mehrwertsteuersatz, Lagerbestand und optionaler Beschreibung anzulegen.
**F-02:** WENN ein Artikel gespeichert wird, DANN MUSS das System die Artikeldaten dauerhaft speichern.
**F-03:** Das System MUSS es ERMÖGLICHEN, bestehende Artikelstammdaten zu bearbeiten.
**F-04:** WENN Änderungen an einem Artikel gespeichert werden, DANN MUSS das System die aktualisierten Daten übernehmen und anzeigen.
**F-05:** Das System MUSS für jeden Artikel eine eindeutige Artikelnummer verwalten.
**F-06:** Das System MUSS den aktuellen Netto-Einzelpreis, Mehrwertsteuersatz und Lagerbestand eines Artikels anzeigen.
## 4.2 Artikel suchen und auswählen
**F-07:** Das System MUSS es ERMÖGLICHEN, Artikel anhand ihrer Artikelnummer oder Bezeichnung zu suchen und auszuwählen.
## 4.3 Lagerbestandsverwaltung
**F-08:** WENN der Lagerbestand eines Artikels den Wert 0 erreicht, DANN MUSS das System den Artikel als nicht verfügbar kennzeichnen.
**F-09:** WENN ein negativer Lagerbestand eingegeben wird, DANN MUSS das System den Speichervorgang VERWEIGERN und eine Fehlermeldung ANZEIGEN.
## 4.4 Löschen von Artikeln
**F-10:** Das System MUSS es ERMÖGLICHEN, bestehende Artikel zu löschen.
**F-11:** WENN ein Artikel gelöscht werden soll UND dieser bereits in einem aktiven oder archivierten Dokument referenziert ist, DANN MUSS das System den Löschvorgang VERWEIGERN und eine Fehlermeldung ANZEIGEN.
# 5\. Nicht-funktionale Anforderungen
## 5.1 Datensicherheit und Zuverlässigkeit
**NF-REL-01:** Das System MUSS Artikel-, Preis- und Lagerbestandsdaten dauerhaft speichern, sodass diese nach einem Neustart der Anwendung unverändert verfügbar sind.
**NF-SEC-01:** Das System MUSS die lokal gespeicherten Artikeldaten so im Dateisystem ablegen, dass sie vor unbefugtem Zugriff geschützt sind.
## 5.2 Benutzbarkeit
**NF-USE-01:** Das Anlegen eines neuen Artikels MUSS von einem erstmaligen Anwender:in OHNE EXTERNE HILFE IN WENIGER ALS 3 MINUTEN abgeschlossen werden können.
**NF-USE-02:** Das System MUSS fehlerhafte Eingaben (z.B. negativer Preis oder negativer Lagerbestand) so kennzeichnen, dass mindestens 80% der Testpersonen den Fehler ohne externe Hilfe korrigieren können
# 6\. Daten und Schnittstellen
## 6.1 Architektonische Designgrundsätze für Datentypen
- Geldbeträge & Steuersätze: Werden ausnahmslos als java.math.BigDecimal mit einem Scale von 2 bzw. 4 und kaufmännischer Rundung ( RoundingMode.HALF_UP ) geführt. Die Nutzung von double oder float ist wegen inhärenter Gleitkomma-Rundungsfehler unzulässig.
- Identifikatoren & Belegnummern: Werden als alphanumerische String -Typen mit festem Format, Präfix und führenden Nullen implementiert (z. B. "R-2026-000124"), um Flexibilität für Formatänderungen zu gewährleisten und mathematische Operationen auszuschließen.
- Datumswerte: Werden konsequent über die moderne API als java.time.LocalDate abgebildet.
- Mengen: Werden für physikalische Stückzahlen als ganzzahliger Primitivtyp int deklariert.
## 6.2 Globale Status-Enumerations
public enum AngebotStatus {
OFFEN, UEBERFUEHRT, VERWORFEN
}
public enum RechnungStatus {
OFFEN, BEZAHLT
}
### 6.2.1 Klasse: Stammdatenobjekte (Kunde und Produkt)
| Attribute | Java-Datentyp | | | Beschreibung/Validierung | | |
| --- | --- | | | --- | | |
| Kunde (Komponente Kundenstamm) | | | | | | |
| kundenId | String | | | Eindeutig, fortlaufende Kundennummer (Primärschlüssel). Alphanumerisch. | | |
| nameFirmenname | String | | | Vollständiger Name des Kunden oder Firmenbezeichnung (Pflichtfeld). | | |
| anschrift | String | | | Vollständiger Postenanschrift (Straße, Hausnummer, PLZ, Ort, Land). | | |
| steuernummerUStId | String | | | Steuernummer oder Umsatzsteuer- Identifikationsnummer für Rechnungslegung. | | |
| eMail | String | | | Validierte E-Mail-Adresse | | |
| telefon | String | | | Telefonnummer im internationalen Format. | | |
| Produkt (Komponente Produktstamm) | | | | | | | |
| produktId | String | | | Eindeutig, fortlaufende Produktnummer (Primärschlüssel). | | |
| bestand | int | | | Aktuell verfügbarer Lagerbestand (darf gemäß F-09 nicht negativ sein). | | |
| bezeichnung | String | | | Standardbezeichnung des Produkts (Pflichtfeld). | | |
| nettoEinzelpreis | BigDecimal | | | Standard-Nettopreis des Produkts (Scale 2, RoundingMode.HALF_UP). | | |
| mehrwertsteuersatz | BigDecimal | | | Standard-Mehrwertsteursatz als Faktor (z.B. 0.19 oder 0.07). Scale 2. | | |
| beschreibung | String | | | Optionale, detaillierte Produktbeschreibung (nullfähig). | | |
### 6.2.2 Klasse: Dokumentposition
Repräsentiert eine spezifische Zeile innerhalb eines beliebigen Belegs. Sie realisiert das funktionale Snapshot-Prinzip (GR-03).
| Attribut | Java-Datentyp | Beschreibung /Validierung |
| ------------------- | ------------- | ------------------------------------------------------------------------------ |
| produktReferenz | String | Fremdschlüssel auf Produkt.produktId . |
| bezeichnung | String | Snapshot: Kopie der Produktbezeichnung zum<br><br>Erstellzeitpunkt des Belegs. |
| menge | int | Stückzahl der georderten Position. Muss strikt > 0 sein. |
| einzelpreisNetto | BigDecimal | Snapshot: Der zum Erstellzeitpunkt gültige Netto- Einzelpreis (Scale 2). |
| steuersatz | BigDecimal | Snapshot: Der zum Erstellzeitpunkt gültige Steuersatz als Faktor (Scale 2). |
| positionssummeNetto | BigDecimal | Berechnetes Attribut: einzelpreisNetto × menge (Scale 2). |
### 6.2.3 Abstrakte Basisklasse: Dokument
Bündelt die gemeinsamen Attribute und Strukturen aller Entitäten der Dokumentenkette.
| Attribut | Java-Datentyp | | Beschreibung / Validierung |
| --- | --- | | --- |
| belegnummer | String | | Eindeutiger Belegschlüssel, vom System lückenlos generiert (GR-01). |
| datum | LocalDate | | Erstelldatum des jeweiligen Dokuments. |
| kundenReferenz | String | | Fremdschlüssel auf Kunde.kundenId (Zuweisung des Vertragspartners). |
| positionen | List&lt;Dokumentposition&gt; | | Kollektion der Belegzeilen. Kardinalität: Mindestens 1 Position erforderlich. |
| summeNetto | BigDecimal | | Summe aller Positionssummen: S positionssummeNetto (Scale 2). |
| summeSteuer | BigDecimal | Summe der Steuerbeträge: S(positionssummeNetto ´ steuersatz) (Scale 2). | |
| summeBrutto | BigDecimal | Gesamtbetrag des Belegs: summeNetto + summeSteuer (Scale 2). | |
### 6.2.4 Konkrete Spezialisierungen (Vererbung von Dokument)
| Klasse | Zusätzliche Attribute (Java-Typ) | Beschreibung / Funktionale Kettenreferenz |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Angebot | gueltigBis: LocalDate<br><br>status: AngebotStatus | Enthält eine Bindefrist. Status verwaltet den Lebenszyklus (Kardinalität zu AB = ). |
| Auftragsbestaetigung | angebotNr: String<br><br>status: DokumentStatus | angebotNr hält die Rückreferenz auf das Angebot (GR-05). Ist optional bei Direkteingabe (Kardinalität zu Lieferschein = ). |
| Lieferschein | Lieferdatum: LocalDate<br><br>auftragsNr: String | auftragsNr verweist auf die AB. **Preisausschluss:** Die Positionen spiegeln nur Bezeichnung und Menge wider (keine Preise!). |
| Rechnung | leistungsdatum: LocalDate<br><br>zahlungsziel: LocalDate<br><br>lieferscheinNr: String<br><br>status: RechnungStatus | lieferscheinNr stellt lückenlose Kette her. Muss zwingend alle rechtlichen Pflichtangaben gem. **§ 14 UStG** enthalten. |
##
## 6.3 Interne Schnittstellen
Zur softwareseitigen Entkopplung der Systemkomponenten werden folgende Java-Interfaces spezifiziert:
package de.fakturierung.komponenten.interfaces;
import java.time.LocalDate;
import java.nio.file.Path;
import java.util.Optional;
// Lesender und entkoppelter Zugriff auf den Kundenstamm
public interface KundenService {
/\*\*
\* Sucht einen Kunden anhand seiner ID.
\* @return Optional mit Kunde, leer falls kundenId nicht existiert (GR-04)
\*/
Optional&lt;Kunde&gt; findeKunde(String kundenId);
}
// Lesender und entkoppelter Zugriff auf den Produktstamm
public interface ProduktService {
/\*\*
\* Sucht ein Produkt anhand seiner ID.
\* @return Optional mit Produkt, leer falls produktId nicht existiert (GR-04)
\*/
Optional&lt;Produkt&gt; findeProdukt(String produktId);
}
// PDF-Generierungskomponente für den Dokumentenexport (IF-03)
public interface PdfExporter {
/\*\*
\* Exportiert ein konkretes Dokument als PDF in das lokale Dateisystem (IF-02).
\*/
void exportiere(Dokument dokument, Path zielDatei) throws java.io.IOException;
}
// Komponenteninterner Generator zur Erfüllung von GR-01
public interface BelegnummernGenerator {
/\*\*
\* Liefert die nächste lückenlose, fortlaufende Nummer für den Belegtyp.
\* Berücksichtigt GoBD-Konformität und das aktuelle Geschäftsjahr.
\*/
String naechsteNummer(Belegtyp typ, int jahr);
}
public enum Belegtyp {
ANGEBOT, AUFTRAGSBESTAETIGUNG, LIEFERSCHEIN, RECHNUNG
}
## 6.4 Externe Schnittstellen
| ID | Schnittstelle | Zweck und technischer Rahmen |
| ----- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IF-01 | Benutzerschnittstelle (GUI) | Interaktive, grafische Desktop-Oberfläche (z. B. JavaFX) zur vollständigen, validierten Bedienung aller Anwendungsmodule durch den Einzelanwender. |
| IF-02 | Lokales Dateisystem | Persistente Speicherung aller Stamm- und Bewegungsdaten (JSON/XML oder lokale eingebettete DB) sowie strukturierte Dateiablage exportierter PDF-Dokumente. |
| IF-03 | Druck-/Export-Schnittstelle | Engine zur programmatischen Generierung druckbarer PDF-Dokumente auf Basis von Dokumentenobjekten sowie direkter Anbindung an den lokalen Betriebssystem-Drucker. |
### 6.4.1 IF-Satzschablone (Spezifikation nach Standard)
- **ID:** IF-02 (Lokales Dateisystem)
- **Schablone:** Das System MUSS eine Datei-Schnittstelle bereitstellen, die es der Persistenzschicht ERMÖGLICHT, auf dem lokalen Dateisystem alle Stamm- und Bewegungsdaten (wie z. B. Produkte) revisionssicher zu persistieren sowie PDF-Exportdokumente strukturiert abzulegen. Die Schnittstelle MUSS standardisierte und plattformunabhängige Dateipfad-Zugriffe (z. B. java.nio.file.Path) verwenden.
# 7\. Systemarchitektur (logisch, grob)
Die Komponente Bestandsmanagement (Gruppe L) kapselt die Fachlogik und die lokale Datenspeicherung für alle Produkte. Sie folgt einer schichtenbasierten Architektur: Ein Serviceinterface (ProduktService) nimmt Anforderungen der Benutzeroberfläche (Gruppe I) und des Belegworkflows (Gruppe K) entgegen. Die Geschäftslogik validiert die Eingaben (z.B. Prüfung auf referenzielle Integrität gemäß GR-04) und gelegiert die dauerhafte Datenspeicherung an eine Persistenzschicht (z.B. ProduktPrpository), welche direkt auf das lokale Dateisystem (IF-02) zugreift. Da die Komponente keine GUI besitzt, existieren hier keine reinen View- oder Controller- Klassen für die Benutzeroberfläche.
## 7.1 Klassendiagramm
Beschreibung zu Abbildung 1: das Klassendiagramm zeigt das Datenmodell und die Serviceschicht der Produktverwaltung. Die zentrale Fachklasse ist Produkt mit den im Lastenheft definierten Attributen produktId, bezeichnung, nettoEinzelpreis, mehrwertsteuersatz, bestand und beschreibung. Das Interface ProduktService definiert die von außen nutzbaren Methoden zur Manipulation der Daten (z. B. findeProdukt, speichereProdukt, loescheProdukt). Eine technische Persistenz-Klasse (ProduktRepository) ist für die Dateioperationen zuständig und kapselt den lokalen Speicherzugriff (IF-02) auf die Festplatte, getrennt von der Fachlogik.
## 7.2 Sequenzdiagramm
Beschreibung zu Abbildung 2: Das Sequenzdiagramm stellt den Ablauf beim Löschen eines Produkts dar, bei dem zwingend die Geschäftsregel GR-04 (Referenzielle Integrität) eingehalten werden muss. Die Benutzeroberfläche (Gruppe I) ruft die Methode loescheProdukt(produktId) des ProduktService auf. Der Service prüft zunächst durch einen Aufruf an die Service-Schnittstelle des Belegworkflows (Gruppe K), ob dieses Produkt bereits in einem aktiven oder archivierten Beleg verwendet wird. Meldet die Komponente Belegworkflow eine Referenz zurück, bricht der ProduktService den Vorgang ab und wirft eine Fehlermeldung (z. B. IllegalStateException), um die Löschsperre durchzusetzen. Ist das Produkt nicht referenziert, wird der Löschbefehl an das ProduktRepository delegiert, welches den Datensatz dauerhaft aus dem Dateisystem entfernt.
# 8\. Testbare Abnahmekriterien
Die folgenden Abnahmekriterien überprüfen die korrekte Umsetzung der funktionalen Kernanforderungen der Komponente Bestandsmanagement (Gruppe L). Da diese Komponente keine eigene GUI besitzt, beziehen sich die Aktionen auf die von aißen aufrufbaren Service- Schnittstellen (ProduktService).
AC-L-01(zu F-01, F-02, NF- REL- 01) - Artikel anlegen und persistent Speichern
- Vorbedingung: Das System (bzw. Die Persistenzschicht) ist betriebsbereit.
- Aktion: Über den ProduktService wird ein neues Produkt mit allen Pflichtattributen (inklusive Netto-Einzelpreis, Mehrwertsteuersatz und einem Lagerbestand > 0) zum Speichern übergeben. Das System wird danach simuliert neu gestartet.
- Erwartetes Ergebnis: Das System nimmt das Produkt fehlerfrei an und legt es im lokalen Dateisystem ab (IF-02). Nach dem Neustart ist das Produkt unverändert über die Suchfunktion des ProduktService abrufbar.
AC-L-02 (zu F-10, F-11, GR-04) - Artikel anlegen und persistent speichern
- Vorbedingung: Das System (bzw. die Persistenzschicht) ist betriebsbereit.
- Aktion: Über den ProduktService wird ein neues Produkt mit allen Pflichtattributen (inklusive Netto-Einzelpreis, Mehrwertsteuersatz und einem Lagerbestand > 0) zum Speichern übergeben. Das System wird danach simuliert neu gestartet.
- Erwartetes Ergebnis: Das System nimmt das Produkt fehlerfrei an und legt es im lokalen Dateisystem ab (IF-02). Nach dem Neustart ist das Produkt unverändert über die Suchfunktion des ProduktService abrufbar.
AC-L-02 (zu F-10, F-11, GR-04) - Durchsetzunng der Löschsperre (Referenzielle Integrität)
- Vorbedingung: Es existiert ein gespeichertes Produkt im System, welches bereits von der Komponente Belegworkflow (Gruppe K) in einer aktiven oder archivierten Rechnung referenziert wird.
- Aktion: Es wird der Befehl gegeben, dieses Produkt über loescheProdukt(produktId) aus dem System zu entfernen.
- Erwartetes Ergebnis: Das System verweigert den Löschvorgang zwingend, wirft eine Fehlermeldung (z. B. IllegalStateException) und das Produkt bleibt im lokalen Dateisystem vollständig erhalten.
AC-L-03 (zu F-06, F-07) - Artikel suchen und anzeigen
- Vorbedingung: Der Datenbestand enthält mindestens 100 Produkte (gemäß Q-01).
- Aktion: Es wird eine Suchanfrage an den ProduktService mit einer bekannten Artikelnummer oder Bezeichnung gestellt.
- Erwartetes Ergebnis: Das System liefert den korrekten Datensatz inklusive des aktuellen Netto-Einzelpreises, des Mehrwertsteuersatzes und des Lagerbestands zurück
AC-L-04 (zu F-08, F-09) - Validierung des Lagerbestands
- Vorbedingung: Ein Produkt ist zur Bearbeitung ausgewählt.
- Aktion: Es wird versucht, den Lagerbestand des Produkts auf einen negativen Wert (z. B. -5) zu ändern und zu speichern.
- Erwartetes Ergebnis: Das System verweigert den Speichervorgang, zeigt eine Validierungsfehlermeldung an und der vorherige (gültige) Bestand bleibt unverändert erhalten. Erreicht der Bestand den Wert 0, wird das Produkt zudem korrekt als "nicht verfügbar" gekennzeichnet.
# 9\. Traceability: Lastenheft (LH) - Pflichtenheft (PH)
| LH- Anforderung | Beschreibung (LH) | PH-Anforderung(en) |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| BA-01 | Kundendatensätze anlegen und verwalten. | Datenstruktur \`Kunde\` / Schnittstelle \`KundenService\` \*(Scope Gruppe J)\* |
| BA-02 | Produktdaten mit Bezeichnung, Preis und Bestand speichern und in Dokumenten verwenden. | \*\*F-01, F-02, F-03, F-04, F-05, F-06\*\* (Artikelstammdaten und Bestandsverwaltung) |
| BA-03 | Angebot für vorhandenen Kunden mit mindestens einer Produktposition erstellen. | Klasse \`Produkt\` und Struktur \`Dokumentposition\` via Schnittstelle \`ProduktService\` |
| BA-04 | Aus einem vorhandenen Angebot eine Rechnung erzeugen (Belegworkflow). | Datenstrukturen \`Angebot\`, \`Rechnung\` und \`Dokumentposition\` im Gesamtdatenmodell |
| Q-01 | Usability: Intuitive Gestaltung, Kernprozesse ohne Schulung in < 10 Min bedienbar. | \*\*NF-USE-01, NF-USE-02\*\* (Eingabefreundlichkeit & interaktive Validierung) |
| Q-02 | Datensicherheit & Zuverlässigkeit: Daten bleiben dauerhaft erhalten und sind vor unbefugtem Zugriff geschützt. | \*\*NF-REL-01, NF-SEC-01\*\* (Sicherung über \`ProduktRepository\`) |
| Q-03 | Revisionssicherheit (GoBD): Final erzeugte Rechnungen systemseitig vor Manipulation schützen. | Datenmodell-Schreibschutz über Status \`RechnungStatus.BEZAHLT\` |
| GR-01 | Fortlaufende lückenlose Rechnungsnummern (GoBD-konform). | Interface \`BelegnummernGenerator\` mit Methode \`naechsteNummer(...)\` |
| GR-02 | Unveränderlichkeit finaler Belege (Storno-Prinzip). | Dokumentenstatus-Sperren im Klassen- und Sequenzdiagramm |
| GR-03 | Snapshot-Prinzip (Preisübernahme): Beim Hinzufügen einer Produktposition zu einem Dokument werden Einzelpreis und Steuersatz im Dokument eingefroren. | Entität \`Dokumentposition\` mit entkoppelten Attributen \`einzelpreisNetto\` und \`steuersatz\` |
| GR-04 | Referenzielle Integrität: Kunden- und Produktdatensätze dürfen nicht gelöscht werden, wenn sie in aktiven oder archivierten Belegen referenziert sind. | \*\*F-10, F-11\*\* (Löschprüfung über \`BelegworkflowService.istProduktReferenziert()\`) |
| GR-05 | Dokumentenketten-Konsistenz: Ein Folgedokument übernimmt die Daten des Vorgängerdokuments und verweist über eine eindeutige Referenz auf dieses. | Generalisierung über Basisklasse \`Dokument\` und UUID-Kettenreferenzen |
| GR-06 | Summenberechnung: Nettosumme = Summe (Menge × Einzelpreis); USt.-Betrag = Nettosumme × Steuersatz; Bruttosumme = Nettosumme + USt.-Betrag. | Berechnungslogik in der Entität \`Dokument\` mittels Datentyp \`BigDecimal\` |
| IF-01 | Benutzerschnittstelle (GUI) zur Bedienung des Systems durch den Einzelanwender. | Java-Interface \`ProduktService\` als Programmschnittstelle zur UI \*(Scope Gruppe I)\* |
| IF-02 | Lokales Dateisystem: Die Speicherung aller Stamm- und Bewegungsdaten erfolgt persistent im lokalen Dateisystem des Rechners. | Klasse \`ProduktRepository\`\*\* mit \`ladeAusDatei()\` und \`speichereInDatei()\` via \`java.nio.file.Path\` |
| IF-03 | Druck-/Export-Schnittstelle zur Erzeugung von Dokumenten (Angebote, Rechnungen) im PDF-Format. | Deklaration des Java-Interface \`PdfExporter\` mit Methode \`exportiere(Dokument, Path)\` |
# 10\. Modultestplan
| TC | Abgedeckte PH-Anforderung | Vorbedingung | Eingabe | Erwartetes Ergebnis |
| ------- | ------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| TC-L-01 | F-01, F-02, F-05 | Leeres Repository | speichereProdukt(produkt) mit neuem Produkt (Preis 10.00, Bestand 50) | Produkt wird fehlerfrei gespeichert. Ein anschließendes findeProdukt(id) liefert exakt dieses Produkt zurück. |
| TC-L-02 | F-05 (Fehlerfall) | Produkt "P-100" existiert bereits im Repository | speichereProdukt(produkt) mit einem neuen Produkt, das ebenfalls die ID "P-100" besitzt | Speichern wird verweigert (z. B. IllegalArgumentException), das alte Produkt "P-100" bleibt unverändert. |
| TC-L-03 | F-03, F-04, F-06 | Produkt "P-101" existiert (Preis: 10.00) | speichereProdukt(produkt) mit geänderten Werten für "P-101" (neuer Preis: 15.00) | Update ist erfolgreich. Nächster Abruf zeigt den aktualisierten Preis und die aktualisierten Werte. |
| TC-L-04 | F-07 | Repository enthält Produkte "P-A" und "P-B" | Aufruf von findeProdukt("P-A") | Das System liefert den korrekten Datensatz für "P-A" zurück; Suche nach unbekannter ID liefert Optional.empty(). |
| TC-L-05 | F-09 (Fehlerfall) | Beliebiger Zustand | speichereProdukt(produkt) mit bestand = -5 | Speichervorgang wird abgebrochen, System wirft eine IllegalArgumentException. |
| TC-L-06 | F-08 | Produkt "P-102" ist in Bearbeitung | speichereProdukt(produkt) mit bestand = 0 | Speichern ist erfolgreich; das Produkt wird intern als "nicht verfügbar" gekennzeichnet (Attributabfrage liefert false). |
| TC-L-07 | F-10 | Produkt "P-103" existiert. Mock von Gruppe K (istProduktReferenziert) liefert false. | loescheProdukt("P-103") | Löschvorgang ist erfolgreich. Ein anschließendes findeProdukt("P-103") liefert kein Ergebnis. |
| TC-L-08 | F-11 (Löschsperre) | Produkt "P-104" existiert. Mock von Gruppe K (istProduktReferenziert) liefert true. | loescheProdukt("P-104") | System wirft zwingend eine IllegalStateException (Löschsperre). Das Produkt "P-104" bleibt im Repository unverändert erhalten. |
| TC-L-09 | F-06, F-07 | Repository enthält exakt 3 verschiedene Produkte | Aufruf von holeAlleProdukte() | Das System liefert eine Liste zurück, die exakt diese 3 Produkte enthält. |
| TC-L-10 | F-01 (Fehlerfall) | Beliebiger Zustand | speichereProdukt(produkt) mit einem ungültigen negativen Netto-Einzelpreis (z.B. -10.00) | Speichervorgang wird abgebrochen, System wirft eine IllegalArgumentException, da Pflichtattribute valide sein müssen. |
# 11\. Anhänge
### 11.1 Abkürzungen
| **Abkürzung** | **Bedeutung** |
| ------------- | ------------------------------------------------------- |
| F-01 bis F-11 | Funktionale Anforderungen |
| NF | Nicht-funktionale Anforderung |
| NF-REL | Nicht-funktionale Anforderung - Zuverlässigkeit |
| NF-SEC | Nicht-funktionale Anforderung - Sicherheit |
| NF-USE | Nicht-funktionale Anforderung - Benutzbarkeit |
| IF | Interface / Schnittstelle |
| USt | Umsatzsteuer |
| ID | Identifier (eindeutige Kennung) |
| GR | Geschäftsregel |
| PH | Pflichtenheft |
| LH | Lastenheft |
| GUI | Graphical User Interface (grafische Benutzeroberfläche) |
| UML | Unified Modeling Language |
| DSGVO | Datenschutz-Grundverordnung |
### 11.2 Begriffe
| **Begriffe** | **Bedeutung** |
| ------------------------ | -------------------------------------------------------------------------------------- |
| Produkt-ID | Eindeutige Kennung eines Produkts |
| Lagerbestand | Aktuell verfügbare Menge eines Produkts |
| Netto-Einzelpreis | Verkaufspreis ohne Mehrwertsteuer |
| Mehrwertsteuersatz | Steueranteil eines Produkts (z. B. 19 %) |
| Persistenz | Dauerhafte Speicherung von Daten |
| ProduktService | Service-Schnittstelle für Produktzugriffe |
| ProduktRepository | Komponente zur Speicherung und zum Laden von Produktdaten |
| Referenzielle Integrität | Produkte dürfen nicht gelöscht werden, wenn sie bereits in Dokumenten verwendet werden |
| BigDecimal | Java-Datentyp für präzise Geldbeträge |
| LocalDate | Java-Datentyp zur Speicherung eines Datums |

View File

@ -0,0 +1,195 @@
Software Engineering 1 | Project Charter | Team 3 |
Datum: 15.04.2026 | Version: 1.2
Weiterleitung zum Git: <https://gitty.informatik.hs-mannheim.de/3028363/SE1_Team_3>
Inhalt
[1\. Dokumentenhistorie 2](#_Toc14205)
[2\. Projektübersicht 2](#_Toc14206)
[2.1 Projektzweck 2](#_Toc14207)
[2.2 Projekthintergrund 2](#_Toc14208)
[3\. Projektziele 2](#_Toc14209)
[3.1 Ziele 2](#_Toc14210)
[3.2 Nicht-Ziele 3](#_Toc14211)
[4\. Business Case 3](#_Toc14212)
[5\. Stakeholder 3](#_Toc14213)
[5.1 Auftraggeber (extern/intern) 3](#_Toc14214)
[5.2 Regulatorisch 3](#_Toc14215)
[5.3 Qualitätsmanagement 4](#_Toc14216)
[6\. Projekt-Team und Rollen 4](#_Toc14217)
[7\. Zeitplan / Meilensteine 5](#_Toc14218)
[8\. Risikomanagement 5](#_Toc14219)
[9\. Budget und Ressourcen 5](#_Toc14220)
[10\. Kommunikations- und Entscheidungswege 6](#_Toc14221)
[11\. Genehmigung / Unterschriften 6](#_Toc14222)
| Autor | Freigebenden | Prüfer |
| ---------------------- | -------------- | ----------------------- |
| Khazanovych, Christian | Winkler, Louis | Prof. Dr. Marmitt, Gerd |
| Entwickler | Entwickler | Modulverantwortlicher |
| 11.05.2026 | 11.05.2026 | Datum, Unterschrift |
# Dokumentenhistorie
| Version | Datum | Autor | Grund der Änderung |
| ------- | ---------- | ---------------------------- | ---------------------------------------------------------------- |
| 1.0 | 08.04.2026 | Christian<br><br>Khazanovych | Erstellung des Dokuments |
| 1.2 | 14.04.2026 | Feyza Yaz | Ergänzung des Qualitätsmanagement (Kapitel 5.3) |
| 1.3 | 11.05.2026 | Christian Khazanovych | Überarbeitung des Dokuments gemäß Feedback von Prof. Dr. Marmitt |
# Projektübersicht
## Projektzweck
Das Programm hilft kleinen Unternehmen und Freiberuflern dabei, Rechnungen und kaufmännische Dokumente einfach zu erstellen. Im Modul Software Engineering 1 lernen wir, wie man Software strukturiert plant, im Team zusammenarbeitet und sauberen Code schreibt.
## Projekthintergrund
Manuelle Rechnungen sind fehleranfällig. Eine Software löst dieses Problem effizient. Als Wirtschaftsinformatiker nutzen wir dieses Projekt, um die Theorie in die Praxis umzusetzen
# Projektziele
## Ziele
| Ziele | Begründung |
| ------------------ | -------------------------------------------------- |
| Produktverwaltung | Speichern von Preisen und Beständen |
| Kundenverwaltung | Speichern von Kundendaten und Kaufhistorie |
| Programmoberfläche | Eine einfache Bedienung für den Nutzer |
| Dokumentenprozess | Von der Erstellung eines Angebots bis zur Rechnung |
## Nicht-Ziele
- Keine Cloud-Anbindung (nur lokal)
- Nur ein Nutzer gleichzeitig (kein Multi-User)
- Zugesicherter Kundensupport und Garantie
- Keine echte Buchhaltung oder Steuerberechnung
- Smartphone bzw. Web-Applikation
# Business Case
Das Projekt adressiert kleine Unternehmen und Freiberufler, die eine einfache und kostengünstige Lösung zur Rechnungsverwaltung benötigen. Im Gegensatz zu vielen abonnementbasierten Cloud-Diensten soll die Anwendung als lokale Desktop-Lösung ohne laufende Kosten nutzbar sein. Dadurch können langfristig Lizenzkosten reduziert und administrative Prozesse effizienter gestaltet werden.
# Stakeholder
## Auftraggeber (extern/intern)
| Rolle | Name | Verantwortlich |
| ------------ | ---------------------- | -------------------------------------------------------------------------------------------- |
| Auftraggeber | Prof. Dr. Gerd Marmitt | Verzeichnis der<br><br>Forderungen,<br><br>Bedingungen, Ziele,<br><br>Bewertung des Projekts |
## Regulatorisch
Das Projekt stellt sicher, dass die erzeugten Dokumente den grundlegenden deutschen Anforderungen an die Rechnungslegung entsprechen:
- Pflichtangaben nach § 14 UstG: Die Software gewährleistet die rechtssichere
Erstellung von Rechnungen durch die Bereitstellung aller notwendigen
Datenfelder (unter anderen fortlaufende Rechnungsnummer,
Steuernummer/Ust-IdNr. Und korrekte Leistungszeiträume)
- Datenschutz (DSGVO): Die Software arbeitet nach dem Prinzip der
Datensparsamkeit. Da keine Cloud-Anbindung besteht, bleiben alle
Kundendaten ausschließlich auf dem lokalen System des Anwenders
- Beleg-Integrität (GoBD): Um eine ordnungsgemäße Buchführung zu unterstützen, sieht das System vor, dass Rechnungen nach dem finalen Erzeugen gegen nachträgliche Änderungen geschützt sind.
## Qualitätsmanagement
Um einen hohen Standard und die Wartbarkeit der Software zu garantieren, folgt das Projekt klaren Qualitätsrichtlinien:
- Qualitätssicherungsmaßnahmen werden spezifisch im Architekturdokument für die jeweiligen Module definiert. Für Kernkomponenten wird ein Peer-Review angestrebt.
- Praxis Check: Neben der automatischen Prüfung erfolgt ein manueller
Funktionstest sowie die Aktualisierung der zugehörigen Dokumentation, um den Wissenstand aktuell zu halten
- Funktionale Vollständigkeit: Die Funktion ist vollständig programmiert und wurde durch automatisierte Komponententest abgesichert
# Projekt-Team und Rollen
| Mitwirkende Personen | Informationen |
| --------------------- | --------------------------------------------------------------------------- |
| Christian Khazanovych | Matrikelnummer: 3028363 Zuständig für: Gruppenleiter Dokumentation |
| Taha Erdogan | Matrikelnummer: 3027955 Zuständig für: Gruppenleiter Backend |
| Meltem Bardakci | Matrikelnummer: 3028424 Zuständig für: Gruppenleiter Anforderungsmanagement |
| Feyza Yaz | Matrikelnummer: 3031021 Zuständig für: Gruppenleiter Qualitätssicherung |
# Zeitplan / Meilensteine
| Meilenstein | Erklärung |
| ------------------------ | --------------------------------------------------------------------- |
| M-01: Konzeption | Project Charter fertigstellen |
| M-02: Kernentwicklung | Entstehung des Produkts mit Fokus auf die Pflichtangaben |
| M-03: Qualitätssicherung | Qualitätsmanagement (Unit Tests & Reviews) |
| M-04: Dokumentation | Fertigstellung der technischen Dokumentation (Pflichtenheft) |
| M-05: Abgabe | Projektpräsentation und Abnahme der Anwendung durch den Auftraggebers |
| Meilenstein | Beschreibung | Termin |
| ----------------------- | -------------------------------- | ---------- |
| M-01 Konzeption | Fertigstellung Project Charter | 15.04.2026 |
| M-02 Kernentwicklung | Umsetzung der Kernfunktionen | 29.04.2026 |
| M-03 Qualitätssicherung | Durchführung von Tests & Reviews | 08.05.2026 |
| M-04 Dokumentation | Fertigstellung Pflichtenheft | 15.05.2026 |
| M-05 Abschluss | Präsentation und Projektabgabe | 30.06.2026 |
# Risikomanagement
| Risikoszenario | Präventionsmaßnahmen |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ressourcenengpass (z.B. krankheitsbedingter Ausfall) | Regelmäßige Code Reviews, um<br><br>sicherzustellen, dass kein „Single Point of<br><br>Failure" entsteht |
| Schnittstellen- & Systemkonflikte | Durch frühe und kontinuierliche Zusammenführung der Softwareteile werden Inkompatibilitäten direkt erkannt |
| Datenverlust beim Endnutzer (Lokale Datenhaltung) | Da keine Cloud-Backups existieren, wird einfache Export-Funktion für Datenbank<br><br>Backups implementiert |
| Versions Konflikte | Benutzung von z.B. Docker, um sicherzustellen, dass die Software auf verschiedenen Umgebungen gleich läuft |
| Mangelnde Benutzerakzeptanz (UI/UX) | Frühzeitige Nutzertests mit dem früh entstandenen Programm durchzuführen, um die Bedienbarkeit sicherzustellen, bevor die Logik fertig programmiert ist |
# Budget und Ressourcen
| Punkte | Beschreibung | Kalkulation |
| -------------- | --------------------------------------- | -------------------------------------------- |
| Personalkosten | Entwicklung, Design & Dokumentation | Pro Person 1-2h die Woche |
| Teamgröße | Vorhandene Arbeitskräfte | 12 Personen |
| Ressourcen | Ressourcen, mit welchen gearbeitet wird | Vorlesungen, Folien und online Recherche |
| Budget | Monetäre Ausgabe | Keine, da es ein Projekt der TH Mannheim ist |
| Laufzeit | Laufzeit des Projekts | 15.04.2026 - 30.06.2026 |
# Kommunikations- und Entscheidungswege
| Wo? | Wieso? |
| ----------- | -------------------------------------------------------------------------------- |
| WhatsApp | Absprache und Ideenaustausch bezüglich des Projekt Charters |
| Discord | Telefonische Absprache bezüglich des Projekts |
| Vorlesungen | Persönliches Treffen des Team und<br><br>Absprache mit dem Professor falls nötig |
# Genehmigung / Unterschriften
Durch die nachfolgenden Unterschriften erklären die Projektbeteiligten ihre Zustimmung zu den Inhalten dieser Projektautorisierung und bestätigen die Übereinstimmung mit den definierten Zielen und Rahmenbedingungen.
| Mitwirkende Personen | Informationen |
| --------------------- | ----------------------------------------------------------------------- |
| Christian Khazanovych | Matrikelnummer: 3028363 Zuständig für: Dokumentation |
| Taha Erdogan | Matrikelnummer: 3027955 Zuständig für: Gruppenleiter Backend |
| Meltem Bardakci | Matrikelnummer: 3028424 Zuständig für: Anforderungsmanagement |
| Feyza Yaz | Matrikelnummer: 3031021 Zuständig für: Gruppenleiter Qualitätssicherung |

73
README.md 100644
View File

@ -0,0 +1,73 @@
# SE1 Team 3 — Fakturierungsanwendung
Dieses Projekt entstand im Rahmen des Moduls Software Engineering 1 an der Hochschule Mannheim (SoSe 2026). Ziel war die arbeitsteilige Entwicklung einer lokalen Desktop-Anwendung zur Rechnungsstellung für Kleinstunternehmen und Freiberufler.
Die Anwendung läuft vollständig offline, speichert alle Daten lokal und unterstützt den kaufmännischen Prozess von der Kundenverwaltung über die Produktpflege bis zur GoBD-konformen Rechnungserstellung.
## Starten
```bash
# Kompilieren (macOS/Linux)
javac -cp "lib/flatlaf.jar" -sourcepath src/main/java -d out $(find src/main/java -name "*.java")
# Kompilieren (Windows PowerShell)
javac -cp "lib/flatlaf.jar" -sourcepath "src\main\java" -d out (Get-ChildItem "src\main\java\kundenverwaltung\*.java").FullName (Get-ChildItem "src\main\java\bestandsmanagement\*.java").FullName (Get-ChildItem "src\main\java\belegworkflow\*.java").FullName (Get-ChildItem "src\main\java\ui\*.java").FullName
# Anwendung starten (macOS/Linux)
java -cp "out:lib/flatlaf.jar" ui.MainWindow
# Anwendung starten (Windows)
java -cp "out;lib/flatlaf.jar" ui.MainWindow
```
## Tests ausfuehren
```bash
# Tests kompilieren (macOS/Linux)
javac -cp "out:lib/junit.jar" -d out $(find src/test/java -name "*.java")
# Tests kompilieren (Windows)
javac -cp "out;lib\junit.jar" -d out src\test\java\kundenverwaltung\KundenVerwaltungTest.java src\test\java\bestandsmanagement\BestandsmanagementTest.java src\test\java\belegworkflow\BelegworkflowTest.java
# Tests ausfuehren
java -jar lib/junit.jar execute --select-class=kundenverwaltung.KundenVerwaltungTest --class-path=out
java -jar lib/junit.jar execute --select-class=bestandsmanagement.BestandsmanagementTest --class-path=out
java -jar lib/junit.jar execute --select-class=belegworkflow.BelegworkflowTest --class-path=out
```
Daten werden lokal als CSV unter `data/` abgelegt (nicht versioniert).
## Projektstruktur
| Paket | Gruppe | Inhalt |
|---|---|---|
| kundenverwaltung | J | Anlegen, Bearbeiten, Suchen und Loeschen von Kundendaten |
| bestandsmanagement | L | Verwaltung von Artikeln, Preisen und Lagerbestaenden |
| belegworkflow | K | Erstellung von Angeboten und Rechnungen, Schreibschutz |
| ui | I | Grafische Benutzeroberfläche mit Swing und FlatLaf |
Quellcode: `src/main/java/` — Tests: `src/test/java/`
## Testergebnisse
| Gruppe | Testklasse | Anzahl | Status |
|---|---|---|---|
| J | KundenVerwaltungTest | 13 | Alle bestanden |
| L | BestandsmanagementTest | 10 | Alle bestanden |
| K | BelegworkflowTest | 10 | Alle bestanden |
33 Testfaelle — alle bestanden.
## Technologie
- Java 25 (Temurin)
- Java Swing + FlatLaf 3.4.1
- JUnit 5
- Lokale CSV-Datenhaltung
## Dokumentation
Alle Projektunterlagen befinden sich unter `Dokumente/`:
- `Lastenheft/`
- `Pflichtenhefte/`
- `Project_Charter/`

View File

@ -1,5 +0,0 @@
K-000002;Ref GmbH;Str. 1;68163;Mannheim;DE111111111;ref@ref.de;0621-111111;;AKTIV
K-000003;Muster GmbH;Hauptstr. 1;68163;Mannheim;DE123456789;info@muster.de;0621-123456;;AKTIV
K-000004;Edit GmbH;Str. 1;68163;Mannheim;DE333333333;neu@edit.de;0621-333333;;AKTIV
K-000006;Test AG;Teststr. 1;68163;Mannheim;DE987654321;test@test.de;0621-654321;;AKTIV
K-000007;Muster GmbH;Hauptstr. 1;68163;Mannheim;DE123456789;info@muster.de;0621-123456;;AKTIV
1 K-000002 Ref GmbH Str. 1 68163 Mannheim DE111111111 ref@ref.de 0621-111111 AKTIV
2 K-000003 Muster GmbH Hauptstr. 1 68163 Mannheim DE123456789 info@muster.de 0621-123456 AKTIV
3 K-000004 Edit GmbH Str. 1 68163 Mannheim DE333333333 neu@edit.de 0621-333333 AKTIV
4 K-000006 Test AG Teststr. 1 68163 Mannheim DE987654321 test@test.de 0621-654321 AKTIV
5 K-000007 Muster GmbH Hauptstr. 1 68163 Mannheim DE123456789 info@muster.de 0621-123456 AKTIV

View File

@ -1 +0,0 @@
7

BIN
lib/flatlaf.jar 100644

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,33 @@
package belegworkflow;
import java.time.LocalDate;
import java.util.List;
public class Angebot extends Dokument {
private LocalDate gueltigBis;
private AngebotStatus status;
public Angebot(String belegnummer, LocalDate datum, String kundenReferenz,
List<Dokumentposition> positionen, LocalDate gueltigBis) {
super(belegnummer, datum, kundenReferenz, positionen);
this.gueltigBis = gueltigBis;
this.status = AngebotStatus.OFFEN;
}
public LocalDate getGueltigBis() { return gueltigBis; }
public AngebotStatus getStatus() { return status; }
public void setStatus(AngebotStatus status) {
this.status = status;
}
@Override
public String toString() {
return String.format("[%s] Angebot | Kunde: %s | Datum: %s | Gueltig bis: %s | Status: %s\n%s\n%s",
belegnummer, kundenReferenz, datum, gueltigBis, status,
positionen.stream().map(Dokumentposition::toString)
.reduce("", (a, b) -> a + "\n" + b),
getSummenAnzeige());
}
}

View File

@ -0,0 +1,7 @@
package belegworkflow;
public enum AngebotStatus {
OFFEN,
UEBERFUEHRT,
VERWORFEN
}

View File

@ -0,0 +1,53 @@
package belegworkflow;
import java.io.*;
import java.nio.file.*;
public class BelegnummernGenerator {
private static final String ANGEBOT_COUNTER = "data/angebot_counter.txt";
private static final String RECHNUNG_COUNTER = "data/rechnung_counter.txt";
private int angebotCounter;
private int rechnungCounter;
public BelegnummernGenerator() {
this.angebotCounter = ladeCounter(ANGEBOT_COUNTER);
this.rechnungCounter = ladeCounter(RECHNUNG_COUNTER);
}
// Nächste lückenlose Angebotsnummer (GR-01)
public String naechsteAngebotsnummer(int jahr) {
angebotCounter++;
speichereCounter(ANGEBOT_COUNTER, angebotCounter);
return String.format("AN-%d-%06d", jahr, angebotCounter);
}
// Nächste lückenlose Rechnungsnummer (GR-01, F-07)
public String naechsteRechnungsnummer(int jahr) {
rechnungCounter++;
speichereCounter(RECHNUNG_COUNTER, rechnungCounter);
return String.format("R-%d-%06d", jahr, rechnungCounter);
}
private int ladeCounter(String datei) {
try {
Path path = Path.of(datei);
if (Files.exists(path)) {
return Integer.parseInt(Files.readString(path).trim());
}
} catch (IOException e) {
System.err.println("Counter konnte nicht geladen werden: " + datei);
}
return 0;
}
private void speichereCounter(String datei, int wert) {
try {
Files.createDirectories(Path.of("data"));
Files.writeString(Path.of(datei), String.valueOf(wert));
} catch (IOException e) {
System.err.println("Counter konnte nicht gespeichert werden: " + datei);
}
}
}

View File

@ -0,0 +1,258 @@
package belegworkflow;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.*;
import java.time.LocalDate;
import java.util.*;
import kundenverwaltung.Kunde;
import kundenverwaltung.KundenServiceImpl;
import bestandsmanagement.Produkt;
import bestandsmanagement.ProduktServiceImpl;
public class BelegworkflowService {
private static final String ANGEBOTE_FILE = "data/angebote.csv";
private static final String ANGEBOTE_POSITIONEN_FILE = "data/angebote_positionen.csv";
private static final String RECHNUNGEN_FILE = "data/rechnungen.csv";
private static final String RECHNUNGEN_POSITIONEN_FILE = "data/rechnungen_positionen.csv";
private List<Angebot> angebote;
private List<Rechnung> rechnungen;
private BelegnummernGenerator generator;
private KundenServiceImpl kundenService;
private ProduktServiceImpl produktService;
public BelegworkflowService() throws IOException {
this.angebote = new ArrayList<>();
this.rechnungen = new ArrayList<>();
this.generator = new BelegnummernGenerator();
this.kundenService = new KundenServiceImpl();
this.produktService = new ProduktServiceImpl();
Files.createDirectories(Path.of("data"));
ladeAngebote();
ladeRechnungen();
}
// Erstellt ein neues Angebot (F-01, F-02, F-03, F-04)
public Angebot erstelleAngebot(String kundenId, List<Dokumentposition> positionen,
LocalDate gueltigBis) {
if (kundenId == null || kundenId.isBlank()) {
System.out.println("Fehler: Kunden-ID darf nicht leer sein.");
return null;
}
if (positionen == null || positionen.isEmpty()) {
System.out.println("Fehler: Mindestens eine Position erforderlich.");
return null;
}
String nr = generator.naechsteAngebotsnummer(LocalDate.now().getYear());
Angebot angebot = new Angebot(nr, LocalDate.now(), kundenId, positionen, gueltigBis);
angebote.add(angebot);
// Belegreferenz beim Kunden setzen (GR-04)
Kunde kunde = kundenService.findeKunde(kundenId);
if (kunde != null) {
kunde.belegHinzufuegen(nr);
}
// Belegreferenz bei allen Produkten setzen (GR-04)
for (Dokumentposition pos : positionen) {
Produkt produkt = produktService.findeProdukt(pos.getProduktReferenz());
if (produkt != null) {
produkt.belegHinzufuegen(nr);
}
}
speichereAngebote();
System.out.println("Angebot erfolgreich erstellt: " + nr);
return angebot;
}
// Erstellt eine Rechnung aus einem Angebot (F-05, F-06, F-07, F-08)
public Rechnung erstelleRechnungAusAngebot(String angebotsNr, LocalDate rechnungsdatum,
LocalDate leistungsdatum, LocalDate zahlungsziel) {
Angebot angebot = findeAngebot(angebotsNr);
if (angebot == null) {
System.out.println("Fehler: Angebot " + angebotsNr + " nicht gefunden.");
return null;
}
if (angebot.getStatus() != AngebotStatus.OFFEN) {
System.out.println("Fehler: Angebot ist nicht mehr offen.");
return null;
}
String nr = generator.naechsteRechnungsnummer(LocalDate.now().getYear());
Rechnung rechnung = new Rechnung(nr, LocalDate.now(), angebot.getKundenReferenz(),
angebot.getPositionen(), rechnungsdatum, leistungsdatum, zahlungsziel, angebotsNr);
// Angebot als überführt markieren (F-05)
angebot.setStatus(AngebotStatus.UEBERFUEHRT);
rechnungen.add(rechnung);
speichereAngebote();
speichereRechnungen();
System.out.println("Rechnung erfolgreich erstellt: " + nr);
return rechnung;
}
// Festschreiben einer Rechnung (F-10)
public void finalisiereRechnung(String rechnungsNr) {
Rechnung rechnung = findeRechnung(rechnungsNr);
if (rechnung == null) {
System.out.println("Fehler: Rechnung " + rechnungsNr + " nicht gefunden.");
return;
}
rechnung.festschreiben();
speichereRechnungen();
System.out.println("Rechnung " + rechnungsNr + " wurde festgeschrieben.");
}
// Prüft ob Kunde in einem Beleg referenziert wird (F-12)
public boolean istKundeReferenziert(String kundenId) {
return angebote.stream().anyMatch(a -> a.getKundenReferenz().equals(kundenId)) ||
rechnungen.stream().anyMatch(r -> r.getKundenReferenz().equals(kundenId));
}
// Prüft ob Produkt in einem Beleg referenziert wird (F-12)
public boolean istProduktReferenziert(String produktId) {
return angebote.stream().anyMatch(a -> a.getPositionen().stream()
.anyMatch(p -> p.getProduktReferenz().equals(produktId))) ||
rechnungen.stream().anyMatch(r -> r.getPositionen().stream()
.anyMatch(p -> p.getProduktReferenz().equals(produktId)));
}
public List<Angebot> alleAngebote() { return new ArrayList<>(angebote); }
public List<Rechnung> alleRechnungen() { return new ArrayList<>(rechnungen); }
public Angebot findeAngebot(String nr) {
return angebote.stream().filter(a -> a.getBelegnummer().equals(nr)).findFirst().orElse(null);
}
public Rechnung findeRechnung(String nr) {
return rechnungen.stream().filter(r -> r.getBelegnummer().equals(nr)).findFirst().orElse(null);
}
// Einfache CSV Persistenz für Angebote
private void speichereAngebote() {
try {
StringBuilder sb = new StringBuilder();
StringBuilder posSb = new StringBuilder();
for (Angebot a : angebote) {
sb.append(a.getBelegnummer()).append(";")
.append(a.getDatum()).append(";")
.append(a.getKundenReferenz()).append(";")
.append(a.getGueltigBis()).append(";")
.append(a.getStatus()).append(";")
.append(a.getSummeNetto()).append(";")
.append(a.getSummeSteuer()).append(";")
.append(a.getSummeBrutto()).append("\n");
for (Dokumentposition pos : a.getPositionen()) {
posSb.append(a.getBelegnummer()).append(";")
.append(pos.getProduktReferenz()).append(";")
.append(pos.getBezeichnung()).append(";")
.append(pos.getMenge()).append(";")
.append(pos.getEinzelpreisNetto()).append(";")
.append(pos.getSteuersatz()).append("\n");
}
}
Files.writeString(Path.of(ANGEBOTE_FILE), sb.toString());
Files.writeString(Path.of(ANGEBOTE_POSITIONEN_FILE), posSb.toString());
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Angebote: " + e.getMessage());
}
}
private void ladeAngebote() {
try {
Path path = Path.of(ANGEBOTE_FILE);
if (!Files.exists(path)) return;
for (String zeile : Files.readAllLines(path)) {
if (zeile.isBlank()) continue;
String[] t = zeile.split(";", -1);
if (t.length < 8) continue;
Angebot a = new Angebot(t[0], LocalDate.parse(t[1]), t[2],
new ArrayList<>(), LocalDate.parse(t[3]));
a.setStatus(AngebotStatus.valueOf(t[4]));
angebote.add(a);
}
Path posPath = Path.of(ANGEBOTE_POSITIONEN_FILE);
if (Files.exists(posPath)) {
for (String zeile : Files.readAllLines(posPath)) {
if (zeile.isBlank()) continue;
String[] t = zeile.split(";", -1);
if (t.length < 6) continue;
Dokumentposition pos = new Dokumentposition(t[1], t[2],
Integer.parseInt(t[3]), new BigDecimal(t[4]), new BigDecimal(t[5]));
angebote.stream().filter(a -> a.getBelegnummer().equals(t[0]))
.findFirst().ifPresent(a -> a.getPositionen().add(pos));
}
}
} catch (IOException e) {
System.err.println("Fehler beim Laden der Angebote: " + e.getMessage());
}
}
private void speichereRechnungen() {
try {
StringBuilder sb = new StringBuilder();
StringBuilder posSb = new StringBuilder();
for (Rechnung r : rechnungen) {
sb.append(r.getBelegnummer()).append(";")
.append(r.getDatum()).append(";")
.append(r.getKundenReferenz()).append(";")
.append(r.getRechnungsdatum()).append(";")
.append(r.getLeistungsdatum()).append(";")
.append(r.getZahlungsziel()).append(";")
.append(r.getAngebotsNr()).append(";")
.append(r.getStatus()).append(";")
.append(r.isFestgeschrieben()).append(";")
.append(r.getSummeNetto()).append(";")
.append(r.getSummeSteuer()).append(";")
.append(r.getSummeBrutto()).append("\n");
for (Dokumentposition pos : r.getPositionen()) {
posSb.append(r.getBelegnummer()).append(";")
.append(pos.getProduktReferenz()).append(";")
.append(pos.getBezeichnung()).append(";")
.append(pos.getMenge()).append(";")
.append(pos.getEinzelpreisNetto()).append(";")
.append(pos.getSteuersatz()).append("\n");
}
}
Files.writeString(Path.of(RECHNUNGEN_FILE), sb.toString());
Files.writeString(Path.of(RECHNUNGEN_POSITIONEN_FILE), posSb.toString());
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Rechnungen: " + e.getMessage());
}
}
private void ladeRechnungen() {
try {
Path path = Path.of(RECHNUNGEN_FILE);
if (!Files.exists(path)) return;
for (String zeile : Files.readAllLines(path)) {
if (zeile.isBlank()) continue;
String[] t = zeile.split(";", -1);
if (t.length < 12) continue;
Rechnung r = new Rechnung(t[0], LocalDate.parse(t[1]), t[2],
new ArrayList<>(), LocalDate.parse(t[3]), LocalDate.parse(t[4]),
LocalDate.parse(t[5]), t[6]);
r.setStatus(RechnungStatus.valueOf(t[7]));
if (Boolean.parseBoolean(t[8])) r.festschreiben();
rechnungen.add(r);
}
Path posPath = Path.of(RECHNUNGEN_POSITIONEN_FILE);
if (Files.exists(posPath)) {
for (String zeile : Files.readAllLines(posPath)) {
if (zeile.isBlank()) continue;
String[] t = zeile.split(";", -1);
if (t.length < 6) continue;
Dokumentposition pos = new Dokumentposition(t[1], t[2],
Integer.parseInt(t[3]), new BigDecimal(t[4]), new BigDecimal(t[5]));
rechnungen.stream().filter(r -> r.getBelegnummer().equals(t[0]))
.findFirst().ifPresent(r -> r.getPositionen().add(pos));
}
}
} catch (IOException e) {
System.err.println("Fehler beim Laden der Rechnungen: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,63 @@
package belegworkflow;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;
public abstract class Dokument {
protected String belegnummer;
protected LocalDate datum;
protected String kundenReferenz;
protected List<Dokumentposition> positionen;
protected BigDecimal summeNetto;
protected BigDecimal summeSteuer;
protected BigDecimal summeBrutto;
protected boolean festgeschrieben;
public Dokument(String belegnummer, LocalDate datum, String kundenReferenz,
List<Dokumentposition> positionen) {
this.belegnummer = belegnummer;
this.datum = datum;
this.kundenReferenz = kundenReferenz;
this.positionen = positionen;
this.festgeschrieben = false;
berechnesSummen();
}
// Berechnet Netto-, Steuer- und Bruttosumme (F-03, F-09, GR-06)
protected void berechnesSummen() {
summeNetto = BigDecimal.ZERO;
summeSteuer = BigDecimal.ZERO;
for (Dokumentposition pos : positionen) {
summeNetto = summeNetto.add(pos.getPositionssummeNetto());
BigDecimal steuer = pos.getPositionssummeNetto()
.multiply(pos.getSteuersatz())
.setScale(2, RoundingMode.HALF_UP);
summeSteuer = summeSteuer.add(steuer);
}
summeNetto = summeNetto.setScale(2, RoundingMode.HALF_UP);
summeSteuer = summeSteuer.setScale(2, RoundingMode.HALF_UP);
summeBrutto = summeNetto.add(summeSteuer).setScale(2, RoundingMode.HALF_UP);
}
public String getBelegnummer() { return belegnummer; }
public LocalDate getDatum() { return datum; }
public String getKundenReferenz() { return kundenReferenz; }
public List<Dokumentposition> getPositionen() { return positionen; }
public BigDecimal getSummeNetto() { return summeNetto; }
public BigDecimal getSummeSteuer() { return summeSteuer; }
public BigDecimal getSummeBrutto() { return summeBrutto; }
public boolean isFestgeschrieben() { return festgeschrieben; }
public void festschreiben() {
this.festgeschrieben = true;
}
public String getSummenAnzeige() {
return String.format("Netto: %.2f EUR | MwSt: %.2f EUR | Brutto: %.2f EUR",
summeNetto, summeSteuer, summeBrutto);
}
}

View File

@ -0,0 +1,39 @@
package belegworkflow;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Dokumentposition {
private String produktReferenz;
private String bezeichnung;
private int menge;
private BigDecimal einzelpreisNetto;
private BigDecimal steuersatz;
private BigDecimal positionssummeNetto;
public Dokumentposition(String produktReferenz, String bezeichnung,
int menge, BigDecimal einzelpreisNetto, BigDecimal steuersatz) {
this.produktReferenz = produktReferenz;
this.bezeichnung = bezeichnung;
this.menge = menge;
this.einzelpreisNetto = einzelpreisNetto.setScale(2, RoundingMode.HALF_UP);
this.steuersatz = steuersatz.setScale(2, RoundingMode.HALF_UP);
this.positionssummeNetto = einzelpreisNetto
.multiply(new BigDecimal(menge))
.setScale(2, RoundingMode.HALF_UP);
}
public String getProduktReferenz() { return produktReferenz; }
public String getBezeichnung() { return bezeichnung; }
public int getMenge() { return menge; }
public BigDecimal getEinzelpreisNetto() { return einzelpreisNetto; }
public BigDecimal getSteuersatz() { return steuersatz; }
public BigDecimal getPositionssummeNetto(){ return positionssummeNetto; }
@Override
public String toString() {
return String.format(" %s x%d | %.2f EUR (netto) | MwSt: %.0f%%",
bezeichnung, menge, einzelpreisNetto,
steuersatz.multiply(new BigDecimal("100")));
}
}

View File

@ -0,0 +1,50 @@
package belegworkflow;
import java.time.LocalDate;
import java.util.List;
public class Rechnung extends Dokument {
private LocalDate rechnungsdatum;
private LocalDate leistungsdatum;
private LocalDate zahlungsziel;
private String angebotsNr;
private RechnungStatus status;
public Rechnung(String belegnummer, LocalDate datum, String kundenReferenz,
List<Dokumentposition> positionen, LocalDate rechnungsdatum,
LocalDate leistungsdatum, LocalDate zahlungsziel, String angebotsNr) {
super(belegnummer, datum, kundenReferenz, positionen);
this.rechnungsdatum = rechnungsdatum;
this.leistungsdatum = leistungsdatum;
this.zahlungsziel = zahlungsziel;
this.angebotsNr = angebotsNr;
this.status = RechnungStatus.OFFEN;
}
public LocalDate getRechnungsdatum() { return rechnungsdatum; }
public LocalDate getLeistungsdatum() { return leistungsdatum; }
public LocalDate getZahlungsziel() { return zahlungsziel; }
public String getAngebotsNr() { return angebotsNr; }
public RechnungStatus getStatus() { return status; }
public void setStatus(RechnungStatus status) {
// Schreibschutz prüfen (F-10)
if (this.festgeschrieben) {
System.out.println("Fehler: Festgeschriebene Rechnung kann nicht geaendert werden.");
return;
}
this.status = status;
}
@Override
public String toString() {
return String.format(
"[%s] Rechnung | Kunde: %s | Datum: %s | Leistung: %s | Zahlung bis: %s | Angebot: %s | Status: %s\n%s\n%s",
belegnummer, kundenReferenz, rechnungsdatum, leistungsdatum,
zahlungsziel, angebotsNr, status,
positionen.stream().map(Dokumentposition::toString)
.reduce("", (a, b) -> a + "\n" + b),
getSummenAnzeige());
}
}

View File

@ -0,0 +1,6 @@
package belegworkflow;
public enum RechnungStatus {
OFFEN,
BEZAHLT
}

View File

@ -0,0 +1,71 @@
package bestandsmanagement;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Produkt {
private String produktId;
private String bezeichnung;
private BigDecimal nettoEinzelpreis;
private BigDecimal mehrwertsteuersatz;
private int bestand;
private String beschreibung;
private List<String> belegReferenzen;
private boolean verfuegbar;
public Produkt(String produktId, String bezeichnung, BigDecimal nettoEinzelpreis,
BigDecimal mehrwertsteuersatz, int bestand, String beschreibung) {
this.produktId = produktId;
this.bezeichnung = bezeichnung;
this.nettoEinzelpreis = nettoEinzelpreis;
this.mehrwertsteuersatz = mehrwertsteuersatz;
this.bestand = bestand;
this.beschreibung = beschreibung;
this.belegReferenzen = new ArrayList<>();
this.verfuegbar = bestand > 0;
}
// Validierung (F-09)
public boolean validiereFelder() {
return bezeichnung != null && !bezeichnung.isBlank() &&
nettoEinzelpreis != null && nettoEinzelpreis.compareTo(BigDecimal.ZERO) >= 0 &&
mehrwertsteuersatz != null && mehrwertsteuersatz.compareTo(BigDecimal.ZERO) >= 0 &&
bestand >= 0;
}
// Getter
public String getProduktId() { return produktId; }
public String getBezeichnung() { return bezeichnung; }
public BigDecimal getNettoEinzelpreis() { return nettoEinzelpreis; }
public BigDecimal getMehrwertsteuersatz(){ return mehrwertsteuersatz; }
public int getBestand() { return bestand; }
public String getBeschreibung() { return beschreibung; }
public List<String> getBelegReferenzen() { return belegReferenzen; }
public boolean isVerfuegbar() { return verfuegbar; }
// Setter
public void setBezeichnung(String bezeichnung) { this.bezeichnung = bezeichnung; }
public void setNettoEinzelpreis(BigDecimal preis) { this.nettoEinzelpreis = preis; }
public void setMehrwertsteuersatz(BigDecimal mwst) { this.mehrwertsteuersatz = mwst; }
public void setBeschreibung(String beschreibung) { this.beschreibung = beschreibung; }
public void setBestand(int bestand) {
this.bestand = bestand;
this.verfuegbar = bestand > 0; // F-08
}
public void belegHinzufuegen(String belegnummer) {
belegReferenzen.add(belegnummer);
}
@Override
public String toString() {
return String.format("[%s] %s | Preis: %.2f EUR | MwSt: %.0f%% | Bestand: %d | %s",
produktId, bezeichnung,
nettoEinzelpreis,
mehrwertsteuersatz.multiply(new BigDecimal("100")),
bestand,
verfuegbar ? "VERFUEGBAR" : "NICHT VERFUEGBAR");
}
}

View File

@ -0,0 +1,43 @@
package bestandsmanagement;
import java.io.*;
import java.nio.file.*;
public class ProduktIdGenerator {
private static final String COUNTER_FILE = "data/produkt_counter.txt";
private int counter;
public ProduktIdGenerator() {
this.counter = ladeCounter();
}
// Gibt die nächste lückenlose Produktnummer zurück
public String naechsteProduktId() {
counter++;
speichereCounter();
return String.format("P-%06d", counter);
}
private int ladeCounter() {
try {
Path path = Path.of(COUNTER_FILE);
if (Files.exists(path)) {
String inhalt = Files.readString(path).trim();
return Integer.parseInt(inhalt);
}
} catch (IOException e) {
System.err.println("Counter konnte nicht geladen werden, starte bei 0.");
}
return 0;
}
private void speichereCounter() {
try {
Files.createDirectories(Path.of("data"));
Files.writeString(Path.of(COUNTER_FILE), String.valueOf(counter));
} catch (IOException e) {
System.err.println("Counter konnte nicht gespeichert werden: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,102 @@
package bestandsmanagement;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.*;
import java.util.*;
public class ProduktRepository {
private static final String DATA_FILE = "data/produkte.csv";
private List<Produkt> produkte;
public ProduktRepository() throws IOException {
this.produkte = new ArrayList<>();
Files.createDirectories(Path.of("data"));
laden();
}
public void speichern(Produkt produkt) {
produkte.add(produkt);
speichereAlle();
}
public void aktualisieren(Produkt produkt) {
speichereAlle();
}
public void loeschen(String produktId) {
produkte.removeIf(p -> p.getProduktId().equals(produktId));
speichereAlle();
}
public List<Produkt> alleProdukte() {
return new ArrayList<>(produkte);
}
public Produkt findeNachId(String produktId) {
return produkte.stream()
.filter(p -> p.getProduktId().equals(produktId))
.findFirst()
.orElse(null);
}
public List<Produkt> sucheNachBezeichnung(String suchbegriff) {
List<Produkt> ergebnis = new ArrayList<>();
for (Produkt p : produkte) {
if (p.getBezeichnung().toLowerCase().contains(suchbegriff.toLowerCase())) {
ergebnis.add(p);
}
}
return ergebnis;
}
private void speichereAlle() {
try {
StringBuilder sb = new StringBuilder();
for (Produkt p : produkte) {
sb.append(p.getProduktId()).append(";")
.append(p.getBezeichnung()).append(";")
.append(p.getNettoEinzelpreis()).append(";")
.append(p.getMehrwertsteuersatz()).append(";")
.append(p.getBestand()).append(";")
.append(p.getBeschreibung() != null ? p.getBeschreibung() : "").append(";")
.append(String.join(",", p.getBelegReferenzen())).append("\n");
}
Files.writeString(Path.of(DATA_FILE), sb.toString());
} catch (IOException e) {
System.err.println("Fehler beim Speichern: " + e.getMessage());
}
}
private void laden() {
try {
Path path = Path.of(DATA_FILE);
if (!Files.exists(path)) return;
List<String> zeilen = Files.readAllLines(path);
for (String zeile : zeilen) {
if (zeile.isBlank()) continue;
String[] teile = zeile.split(";", -1);
if (teile.length < 7) continue;
Produkt p = new Produkt(
teile[0],
teile[1],
new BigDecimal(teile[2]),
new BigDecimal(teile[3]),
Integer.parseInt(teile[4]),
teile[5].isBlank() ? null : teile[5]
);
if (!teile[6].isBlank()) {
for (String beleg : teile[6].split(",")) {
p.belegHinzufuegen(beleg);
}
}
produkte.add(p);
}
} catch (IOException e) {
System.err.println("Fehler beim Laden: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,114 @@
package bestandsmanagement;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.*;
import java.util.List;
public class ProduktServiceImpl {
private final ProduktRepository repository;
private final ProduktIdGenerator idGenerator;
public ProduktServiceImpl() throws IOException {
this.repository = new ProduktRepository();
this.idGenerator = new ProduktIdGenerator();
}
public Produkt produktAnlegen(String bezeichnung, BigDecimal nettoEinzelpreis,
BigDecimal mehrwertsteuersatz, int bestand, String beschreibung) {
if (bestand < 0) {
System.out.println("Fehler: Negativer Lagerbestand ist nicht erlaubt.");
return null;
}
Produkt produkt = new Produkt("TEMP", bezeichnung, nettoEinzelpreis,
mehrwertsteuersatz, bestand, beschreibung);
if (!produkt.validiereFelder()) {
System.out.println("Fehler: Alle Pflichtfelder muessen ausgefuellt sein.");
return null;
}
String id = idGenerator.naechsteProduktId();
Produkt neuesProdukt = new Produkt(id, bezeichnung, nettoEinzelpreis,
mehrwertsteuersatz, bestand, beschreibung);
repository.speichern(neuesProdukt);
System.out.println("Artikel erfolgreich angelegt: " + neuesProdukt);
return neuesProdukt;
}
public boolean produktBearbeiten(String produktId, String bezeichnung,
BigDecimal nettoEinzelpreis, BigDecimal mehrwertsteuersatz,
int bestand, String beschreibung) {
Produkt produkt = repository.findeNachId(produktId);
if (produkt == null) {
System.out.println("Fehler: Artikel mit ID " + produktId + " nicht gefunden.");
return false;
}
if (bestand < 0) {
System.out.println("Fehler: Negativer Lagerbestand ist nicht erlaubt.");
return false;
}
produkt.setBezeichnung(bezeichnung);
produkt.setNettoEinzelpreis(nettoEinzelpreis);
produkt.setMehrwertsteuersatz(mehrwertsteuersatz);
produkt.setBestand(bestand);
produkt.setBeschreibung(beschreibung);
if (!produkt.validiereFelder()) {
System.out.println("Fehler: Alle Pflichtfelder muessen ausgefuellt sein.");
return false;
}
repository.aktualisieren(produkt);
System.out.println("Artikel erfolgreich aktualisiert: " + produkt);
return true;
}
public boolean produktLoeschen(String produktId) {
Produkt produkt = repository.findeNachId(produktId);
if (produkt == null) {
System.out.println("Fehler: Artikel mit ID " + produktId + " nicht gefunden.");
return false;
}
// In-Memory-Belegliste und CSV-Dateien prüfen (GR-04)
if (!produkt.getBelegReferenzen().isEmpty() || istProduktInBelegReferenziert(produktId)) {
System.out.println("Fehler: Artikel ist in einem Beleg referenziert und kann nicht geloescht werden.");
return false;
}
repository.loeschen(produktId);
System.out.println("Artikel " + produktId + " erfolgreich geloescht.");
return true;
}
private boolean istProduktInBelegReferenziert(String produktId) {
try {
String[] positionenDateien = {
"data/angebote_positionen.csv",
"data/rechnungen_positionen.csv"
};
for (String datei : positionenDateien) {
Path path = Path.of(datei);
if (Files.exists(path)) {
for (String zeile : Files.readAllLines(path)) {
String[] t = zeile.split(";", -1);
if (t.length >= 2 && t[1].equals(produktId)) return true;
}
}
}
} catch (IOException e) {
System.err.println("Warnung: Belegpruefung konnte nicht durchgefuehrt werden.");
}
return false;
}
public List<Produkt> alleProdukte() {
return repository.alleProdukte();
}
public List<Produkt> sucheNachBezeichnung(String suchbegriff) {
return repository.sucheNachBezeichnung(suchbegriff);
}
public Produkt findeProdukt(String produktId) {
return repository.findeNachId(produktId);
}
}

View File

@ -1,6 +1,7 @@
package kundenverwaltung; package kundenverwaltung;
import java.io.IOException; import java.io.IOException;
import java.nio.file.*;
import java.util.List; import java.util.List;
public class KundenServiceImpl { public class KundenServiceImpl {
@ -13,18 +14,13 @@ public class KundenServiceImpl {
this.idGenerator = new KundenIdGenerator(); this.idGenerator = new KundenIdGenerator();
} }
// Legt einen neuen Kunden an (F-01, F-02, F-03, F-04)
public Kunde kundeAnlegen(String name, String strasse, String plz, String ort, public Kunde kundeAnlegen(String name, String strasse, String plz, String ort,
String steuerNummer, String email, String telefon) { String steuerNummer, String email, String telefon) {
Kunde kunde = new Kunde("TEMP", name, strasse, plz, ort, steuerNummer, email, telefon); Kunde kunde = new Kunde("TEMP", name, strasse, plz, ort, steuerNummer, email, telefon);
// Pflichtfeldprüfung (F-03)
if (!kunde.validiereFelder()) { if (!kunde.validiereFelder()) {
System.out.println("Fehler: Alle Pflichtfelder müssen ausgefüllt sein."); System.out.println("Fehler: Alle Pflichtfelder müssen ausgefüllt sein.");
return null; return null;
} }
// ID vergeben (F-02)
String id = idGenerator.naechsteKundenId(); String id = idGenerator.naechsteKundenId();
Kunde neuerKunde = new Kunde(id, name, strasse, plz, ort, steuerNummer, email, telefon); Kunde neuerKunde = new Kunde(id, name, strasse, plz, ort, steuerNummer, email, telefon);
repository.speichern(neuerKunde); repository.speichern(neuerKunde);
@ -32,7 +28,6 @@ public class KundenServiceImpl {
return neuerKunde; return neuerKunde;
} }
// Bearbeitet einen bestehenden Kunden (F-05, F-06, F-07)
public boolean kundeBearbeiten(String kundenId, String name, String strasse, String plz, public boolean kundeBearbeiten(String kundenId, String name, String strasse, String plz,
String ort, String steuerNummer, String email, String telefon) { String ort, String steuerNummer, String email, String telefon) {
Kunde kunde = repository.findeNachId(kundenId); Kunde kunde = repository.findeNachId(kundenId);
@ -40,7 +35,6 @@ public class KundenServiceImpl {
System.out.println("Fehler: Kunde mit ID " + kundenId + " nicht gefunden."); System.out.println("Fehler: Kunde mit ID " + kundenId + " nicht gefunden.");
return false; return false;
} }
kunde.setName(name); kunde.setName(name);
kunde.setStrasse(strasse); kunde.setStrasse(strasse);
kunde.setPlz(plz); kunde.setPlz(plz);
@ -48,19 +42,15 @@ public class KundenServiceImpl {
kunde.setSteuerNummer(steuerNummer); kunde.setSteuerNummer(steuerNummer);
kunde.setEmail(email); kunde.setEmail(email);
kunde.setTelefon(telefon); kunde.setTelefon(telefon);
// Pflichtfeldprüfung nach Änderung (F-06)
if (!kunde.validiereFelder()) { if (!kunde.validiereFelder()) {
System.out.println("Fehler: Alle Pflichtfelder müssen ausgefüllt sein."); System.out.println("Fehler: Alle Pflichtfelder müssen ausgefüllt sein.");
return false; return false;
} }
repository.aktualisieren(kunde); repository.aktualisieren(kunde);
System.out.println("Kunde erfolgreich aktualisiert: " + kunde); System.out.println("Kunde erfolgreich aktualisiert: " + kunde);
return true; return true;
} }
// Löscht einen Kunden mit Integritätsprüfung (F-11, F-12, GR-04)
public boolean kundeLoschen(String kundenId) { public boolean kundeLoschen(String kundenId) {
Kunde kunde = repository.findeNachId(kundenId); Kunde kunde = repository.findeNachId(kundenId);
if (kunde == null) { if (kunde == null) {
@ -68,10 +58,9 @@ public class KundenServiceImpl {
return false; return false;
} }
// Referenzielle Integrität prüfen (GR-04) // In-Memory-Belegliste und CSV-Dateien prüfen (GR-04)
if (!kunde.getBelegReferenzen().isEmpty()) { if (!kunde.getBelegReferenzen().isEmpty() || istKundeInBelegReferenziert(kundenId)) {
System.out.println("Fehler: Kunde kann nicht gelöscht werden, da er in " + System.out.println("Fehler: Kunde ist in einem Beleg referenziert und kann nicht geloescht werden.");
kunde.getBelegReferenzen().size() + " Beleg(en) referenziert wird.");
return false; return false;
} }
@ -80,22 +69,41 @@ public class KundenServiceImpl {
return true; return true;
} }
// Gibt alle Kunden zurück (F-08) private boolean istKundeInBelegReferenziert(String kundenId) {
try {
Path angebotePath = Path.of("data/angebote.csv");
Path rechnungenPath = Path.of("data/rechnungen.csv");
if (Files.exists(angebotePath)) {
for (String zeile : Files.readAllLines(angebotePath)) {
String[] teile = zeile.split(";", -1);
if (teile.length >= 3 && teile[2].equals(kundenId)) return true;
}
}
if (Files.exists(rechnungenPath)) {
for (String zeile : Files.readAllLines(rechnungenPath)) {
String[] teile = zeile.split(";", -1);
if (teile.length >= 3 && teile[2].equals(kundenId)) return true;
}
}
} catch (IOException e) {
System.err.println("Warnung: Belegpruefung konnte nicht durchgefuehrt werden.");
}
return false;
}
public List<Kunde> alleKunden() { public List<Kunde> alleKunden() {
return repository.alleKunden(); return repository.alleKunden();
} }
// Sucht Kunden nach Name (F-09)
public List<Kunde> sucheNachName(String suchbegriff) { public List<Kunde> sucheNachName(String suchbegriff) {
return repository.sucheNachName(suchbegriff); return repository.sucheNachName(suchbegriff);
} }
// Gibt einen Kunden anhand der ID zurück (F-10)
public Kunde findeKunde(String kundenId) { public Kunde findeKunde(String kundenId) {
return repository.findeNachId(kundenId); return repository.findeNachId(kundenId);
} }
// Zeigt die Transaktionshistorie eines Kunden (F-13)
public void zeigeTransaktionshistorie(String kundenId) { public void zeigeTransaktionshistorie(String kundenId) {
Kunde kunde = repository.findeNachId(kundenId); Kunde kunde = repository.findeNachId(kundenId);
if (kunde == null) { if (kunde == null) {
@ -111,4 +119,4 @@ public class KundenServiceImpl {
} }
} }
} }
} }

View File

@ -1,52 +1,78 @@
package kundenverwaltung; package kundenverwaltung;
import bestandsmanagement.Produkt;
import bestandsmanagement.ProduktServiceImpl;
import belegworkflow.*;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
public class Main { public class Main {
private static KundenServiceImpl service; private static KundenServiceImpl kundenService;
private static ProduktServiceImpl produktService;
private static BelegworkflowService belegService;
private static Scanner scanner = new Scanner(System.in); private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) { public static void main(String[] args) {
try { try {
service = new KundenServiceImpl(); kundenService = new KundenServiceImpl();
produktService = new ProduktServiceImpl();
belegService = new BelegworkflowService();
System.out.println("==========================================="); System.out.println("===========================================");
System.out.println(" Kundenverwaltung - Gruppe J - Team 3"); System.out.println(" Fakturierungsanwendung - Team 3");
System.out.println("==========================================="); System.out.println("===========================================");
menueSchleife(); hauptmenue();
} catch (IOException e) { } catch (IOException e) {
System.err.println("Fehler beim Starten: " + e.getMessage()); System.err.println("Fehler beim Starten: " + e.getMessage());
} }
} }
private static void menueSchleife() { private static void hauptmenue() {
while (true) { while (true) {
System.out.println("\n--- HAUPTMENUE ---"); System.out.println("\n=== HAUPTMENUE ===");
System.out.println("1. Kunde anlegen"); System.out.println("1. Kundenverwaltung");
System.out.println("2. Alle Kunden anzeigen"); System.out.println("2. Bestandsmanagement");
System.out.println("3. Kunde suchen (nach Name)"); System.out.println("3. Belegworkflow");
System.out.println("4. Kunde bearbeiten");
System.out.println("5. Kunde loeschen");
System.out.println("6. Transaktionshistorie anzeigen");
System.out.println("0. Beenden"); System.out.println("0. Beenden");
System.out.print("Auswahl: "); System.out.print("Auswahl: ");
String eingabe = scanner.nextLine().trim(); switch (scanner.nextLine().trim()) {
case "1" -> kundenmenue();
case "2" -> produktmenue();
case "3" -> belegmenue();
case "0" -> { System.out.println("Programm beendet."); return; }
default -> System.out.println("Ungueltige Eingabe.");
}
}
}
switch (eingabe) { // ─── KUNDENVERWALTUNG ───────────────────────────────────────────
private static void kundenmenue() {
while (true) {
System.out.println("\n--- KUNDENVERWALTUNG ---");
System.out.println("1. Kunde anlegen");
System.out.println("2. Alle Kunden anzeigen");
System.out.println("3. Kunde suchen");
System.out.println("4. Kunde bearbeiten");
System.out.println("5. Kunde loeschen");
System.out.println("6. Transaktionshistorie");
System.out.println("0. Zurueck");
System.out.print("Auswahl: ");
switch (scanner.nextLine().trim()) {
case "1" -> kundeAnlegen(); case "1" -> kundeAnlegen();
case "2" -> alleKundenAnzeigen(); case "2" -> alleKundenAnzeigen();
case "3" -> kundenSuchen(); case "3" -> kundenSuchen();
case "4" -> kundeBearbeiten(); case "4" -> kundeBearbeiten();
case "5" -> kundeLoschen(); case "5" -> kundeLoschen();
case "6" -> transaktionshistorie(); case "6" -> transaktionshistorie();
case "0" -> { case "0" -> { return; }
System.out.println("Programm beendet."); default -> System.out.println("Ungueltige Eingabe.");
return;
}
default -> System.out.println("Ungueltige Eingabe, bitte erneut versuchen.");
} }
} }
} }
@ -67,48 +93,32 @@ public class Main {
String email = scanner.nextLine(); String email = scanner.nextLine();
System.out.print("Telefon: "); System.out.print("Telefon: ");
String telefon = scanner.nextLine(); String telefon = scanner.nextLine();
kundenService.kundeAnlegen(name, strasse, plz, ort, steuerNummer, email, telefon);
service.kundeAnlegen(name, strasse, plz, ort, steuerNummer, email, telefon);
} }
private static void alleKundenAnzeigen() { private static void alleKundenAnzeigen() {
System.out.println("\n--- ALLE KUNDEN ---"); System.out.println("\n--- ALLE KUNDEN ---");
List<Kunde> kunden = service.alleKunden(); List<Kunde> kunden = kundenService.alleKunden();
if (kunden.isEmpty()) { if (kunden.isEmpty()) { System.out.println("Keine Kunden vorhanden."); return; }
System.out.println("Keine Kunden vorhanden."); kunden.forEach(System.out::println);
return;
}
for (Kunde k : kunden) {
System.out.println(k);
}
System.out.println("Gesamt: " + kunden.size() + " Kunde(n)."); System.out.println("Gesamt: " + kunden.size() + " Kunde(n).");
} }
private static void kundenSuchen() { private static void kundenSuchen() {
System.out.println("\n--- KUNDE SUCHEN ---"); System.out.println("\n--- KUNDE SUCHEN ---");
System.out.print("Suchbegriff (Name): "); System.out.print("Suchbegriff (Name): ");
String suchbegriff = scanner.nextLine(); List<Kunde> ergebnis = kundenService.sucheNachName(scanner.nextLine());
List<Kunde> ergebnis = service.sucheNachName(suchbegriff); if (ergebnis.isEmpty()) { System.out.println("Keine Kunden gefunden."); return; }
if (ergebnis.isEmpty()) { ergebnis.forEach(System.out::println);
System.out.println("Keine Kunden gefunden.");
return;
}
for (Kunde k : ergebnis) {
System.out.println(k);
}
} }
private static void kundeBearbeiten() { private static void kundeBearbeiten() {
System.out.println("\n--- KUNDE BEARBEITEN ---"); System.out.println("\n--- KUNDE BEARBEITEN ---");
System.out.print("Kunden-ID eingeben: "); System.out.print("Kunden-ID: ");
String id = scanner.nextLine(); String id = scanner.nextLine();
Kunde kunde = service.findeKunde(id); Kunde kunde = kundenService.findeKunde(id);
if (kunde == null) { if (kunde == null) { System.out.println("Kunde nicht gefunden."); return; }
System.out.println("Kunde nicht gefunden.");
return;
}
System.out.println("Aktuell: " + kunde); System.out.println("Aktuell: " + kunde);
System.out.println("Neue Werte eingeben (Enter = Feld uebernehmen):");
System.out.print("Name [" + kunde.getName() + "]: "); System.out.print("Name [" + kunde.getName() + "]: ");
String name = scanner.nextLine(); String name = scanner.nextLine();
@ -138,20 +148,212 @@ public class Main {
String telefon = scanner.nextLine(); String telefon = scanner.nextLine();
if (telefon.isBlank()) telefon = kunde.getTelefon(); if (telefon.isBlank()) telefon = kunde.getTelefon();
service.kundeBearbeiten(id, name, strasse, plz, ort, steuerNummer, email, telefon); kundenService.kundeBearbeiten(id, name, strasse, plz, ort, steuerNummer, email, telefon);
} }
private static void kundeLoschen() { private static void kundeLoschen() {
System.out.println("\n--- KUNDE LOESCHEN ---"); System.out.println("\n--- KUNDE LOESCHEN ---");
System.out.print("Kunden-ID eingeben: "); System.out.print("Kunden-ID: ");
String id = scanner.nextLine(); kundenService.kundeLoschen(scanner.nextLine());
service.kundeLoschen(id);
} }
private static void transaktionshistorie() { private static void transaktionshistorie() {
System.out.println("\n--- TRANSAKTIONSHISTORIE ---"); System.out.println("\n--- TRANSAKTIONSHISTORIE ---");
System.out.print("Kunden-ID eingeben: "); System.out.print("Kunden-ID: ");
kundenService.zeigeTransaktionshistorie(scanner.nextLine());
}
// ─── BESTANDSMANAGEMENT ─────────────────────────────────────────
private static void produktmenue() {
while (true) {
System.out.println("\n--- BESTANDSMANAGEMENT ---");
System.out.println("1. Artikel anlegen");
System.out.println("2. Alle Artikel anzeigen");
System.out.println("3. Artikel suchen");
System.out.println("4. Artikel bearbeiten");
System.out.println("5. Artikel loeschen");
System.out.println("0. Zurueck");
System.out.print("Auswahl: ");
switch (scanner.nextLine().trim()) {
case "1" -> artikelAnlegen();
case "2" -> alleArtikelAnzeigen();
case "3" -> artikelSuchen();
case "4" -> artikelBearbeiten();
case "5" -> artikelLoeschen();
case "0" -> { return; }
default -> System.out.println("Ungueltige Eingabe.");
}
}
}
private static void artikelAnlegen() {
System.out.println("\n--- ARTIKEL ANLEGEN ---");
System.out.print("Bezeichnung: ");
String bezeichnung = scanner.nextLine();
System.out.print("Netto-Einzelpreis (z.B. 19.99): ");
BigDecimal preis = new BigDecimal(scanner.nextLine().replace(",", "."));
System.out.print("Mehrwertsteuersatz (z.B. 0.19 fuer 19%): ");
BigDecimal mwst = new BigDecimal(scanner.nextLine().replace(",", "."));
System.out.print("Lagerbestand: ");
int bestand = Integer.parseInt(scanner.nextLine().trim());
System.out.print("Beschreibung (optional, Enter ueberspringen): ");
String beschreibung = scanner.nextLine();
if (beschreibung.isBlank()) beschreibung = null;
produktService.produktAnlegen(bezeichnung, preis, mwst, bestand, beschreibung);
}
private static void alleArtikelAnzeigen() {
System.out.println("\n--- ALLE ARTIKEL ---");
List<Produkt> produkte = produktService.alleProdukte();
if (produkte.isEmpty()) { System.out.println("Keine Artikel vorhanden."); return; }
produkte.forEach(System.out::println);
System.out.println("Gesamt: " + produkte.size() + " Artikel.");
}
private static void artikelSuchen() {
System.out.println("\n--- ARTIKEL SUCHEN ---");
System.out.print("Suchbegriff (Bezeichnung): ");
List<Produkt> ergebnis = produktService.sucheNachBezeichnung(scanner.nextLine());
if (ergebnis.isEmpty()) { System.out.println("Keine Artikel gefunden."); return; }
ergebnis.forEach(System.out::println);
}
private static void artikelBearbeiten() {
System.out.println("\n--- ARTIKEL BEARBEITEN ---");
System.out.print("Artikel-ID: ");
String id = scanner.nextLine(); String id = scanner.nextLine();
service.zeigeTransaktionshistorie(id); Produkt produkt = produktService.findeProdukt(id);
if (produkt == null) { System.out.println("Artikel nicht gefunden."); return; }
System.out.println("Aktuell: " + produkt);
System.out.print("Bezeichnung [" + produkt.getBezeichnung() + "]: ");
String bezeichnung = scanner.nextLine();
if (bezeichnung.isBlank()) bezeichnung = produkt.getBezeichnung();
System.out.print("Netto-Einzelpreis [" + produkt.getNettoEinzelpreis() + "]: ");
String preisStr = scanner.nextLine();
BigDecimal preis = preisStr.isBlank() ? produkt.getNettoEinzelpreis() : new BigDecimal(preisStr.replace(",", "."));
System.out.print("Mehrwertsteuersatz [" + produkt.getMehrwertsteuersatz() + "]: ");
String mwstStr = scanner.nextLine();
BigDecimal mwst = mwstStr.isBlank() ? produkt.getMehrwertsteuersatz() : new BigDecimal(mwstStr.replace(",", "."));
System.out.print("Lagerbestand [" + produkt.getBestand() + "]: ");
String bestandStr = scanner.nextLine();
int bestand = bestandStr.isBlank() ? produkt.getBestand() : Integer.parseInt(bestandStr.trim());
System.out.print("Beschreibung [" + produkt.getBeschreibung() + "]: ");
String beschreibung = scanner.nextLine();
if (beschreibung.isBlank()) beschreibung = produkt.getBeschreibung();
produktService.produktBearbeiten(id, bezeichnung, preis, mwst, bestand, beschreibung);
}
private static void artikelLoeschen() {
System.out.println("\n--- ARTIKEL LOESCHEN ---");
System.out.print("Artikel-ID: ");
produktService.produktLoeschen(scanner.nextLine());
}
// ─── BELEGWORKFLOW ──────────────────────────────────────────────
private static void belegmenue() {
while (true) {
System.out.println("\n--- BELEGWORKFLOW ---");
System.out.println("1. Angebot erstellen");
System.out.println("2. Alle Angebote anzeigen");
System.out.println("3. Rechnung aus Angebot erstellen");
System.out.println("4. Alle Rechnungen anzeigen");
System.out.println("5. Rechnung festschreiben");
System.out.println("0. Zurueck");
System.out.print("Auswahl: ");
switch (scanner.nextLine().trim()) {
case "1" -> angebotErstellen();
case "2" -> alleAngeboteAnzeigen();
case "3" -> rechnungAusAngebot();
case "4" -> alleRechnungenAnzeigen();
case "5" -> rechnungFestschreiben();
case "0" -> { return; }
default -> System.out.println("Ungueltige Eingabe.");
}
}
}
private static void angebotErstellen() {
System.out.println("\n--- ANGEBOT ERSTELLEN ---");
System.out.print("Kunden-ID: ");
String kundenId = scanner.nextLine();
List<Dokumentposition> positionen = new ArrayList<>();
while (true) {
System.out.println("Position hinzufuegen (Enter zum Beenden):");
System.out.print("Produkt-ID (oder Enter zum Beenden): ");
String produktId = scanner.nextLine();
if (produktId.isBlank()) break;
Produkt produkt = produktService.findeProdukt(produktId);
if (produkt == null) {
System.out.println("Produkt nicht gefunden.");
continue;
}
System.out.print("Menge: ");
int menge = Integer.parseInt(scanner.nextLine().trim());
positionen.add(new Dokumentposition(
produkt.getProduktId(),
produkt.getBezeichnung(),
menge,
produkt.getNettoEinzelpreis(),
produkt.getMehrwertsteuersatz()
));
System.out.println("Position hinzugefuegt: " + produkt.getBezeichnung());
}
if (positionen.isEmpty()) {
System.out.println("Fehler: Mindestens eine Position erforderlich.");
return;
}
System.out.print("Gueltig bis (YYYY-MM-DD): ");
LocalDate gueltigBis = LocalDate.parse(scanner.nextLine());
belegService.erstelleAngebot(kundenId, positionen, gueltigBis);
}
private static void alleAngeboteAnzeigen() {
System.out.println("\n--- ALLE ANGEBOTE ---");
List<Angebot> angebote = belegService.alleAngebote();
if (angebote.isEmpty()) { System.out.println("Keine Angebote vorhanden."); return; }
angebote.forEach(a -> System.out.println(a.getBelegnummer() + " | Kunde: " +
a.getKundenReferenz() + " | Status: " + a.getStatus()));
}
private static void rechnungAusAngebot() {
System.out.println("\n--- RECHNUNG AUS ANGEBOT ---");
System.out.print("Angebotsnummer: ");
String angebotsNr = scanner.nextLine();
System.out.print("Rechnungsdatum (YYYY-MM-DD): ");
LocalDate rechnungsdatum = LocalDate.parse(scanner.nextLine());
System.out.print("Leistungsdatum (YYYY-MM-DD): ");
LocalDate leistungsdatum = LocalDate.parse(scanner.nextLine());
System.out.print("Zahlungsziel (YYYY-MM-DD): ");
LocalDate zahlungsziel = LocalDate.parse(scanner.nextLine());
belegService.erstelleRechnungAusAngebot(angebotsNr, rechnungsdatum, leistungsdatum, zahlungsziel);
}
private static void alleRechnungenAnzeigen() {
System.out.println("\n--- ALLE RECHNUNGEN ---");
List<Rechnung> rechnungen = belegService.alleRechnungen();
if (rechnungen.isEmpty()) { System.out.println("Keine Rechnungen vorhanden."); return; }
rechnungen.forEach(r -> System.out.println(r.getBelegnummer() + " | Kunde: " +
r.getKundenReferenz() + " | Status: " + r.getStatus() +
" | Festgeschrieben: " + r.isFestgeschrieben()));
}
private static void rechnungFestschreiben() {
System.out.println("\n--- RECHNUNG FESTSCHREIBEN ---");
System.out.print("Rechnungsnummer: ");
belegService.finalisiereRechnung(scanner.nextLine());
} }
} }

View File

@ -0,0 +1,227 @@
package ui;
import belegworkflow.*;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class DokumentePanel extends JPanel {
private BelegworkflowService service;
private JTable tabelle;
private DefaultTableModel tabellenModell;
public DokumentePanel() {
try { service = new BelegworkflowService(); }
catch (IOException e) { e.printStackTrace(); }
setLayout(new BorderLayout(10, 10));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
setBackground(new Color(244, 247, 255));
add(erstelleKopf(), BorderLayout.NORTH);
add(erstelleTabelle(), BorderLayout.CENTER);
add(erstelleAktionen(), BorderLayout.SOUTH);
}
private JPanel erstelleKopf() {
JPanel kopf = new JPanel(new BorderLayout());
kopf.setBackground(new Color(244, 247, 255));
JLabel titel = new JLabel("Dokumentenübersicht");
titel.setFont(new Font("Cambria", Font.BOLD, 20));
titel.setForeground(new Color(30, 39, 97));
kopf.add(titel, BorderLayout.WEST);
JButton refresh = erstelleButton("Aktualisieren", new Color(74, 144, 217));
refresh.addActionListener(e -> ladeDokumente());
JPanel rechts = new JPanel(new FlowLayout(FlowLayout.RIGHT));
rechts.setBackground(new Color(244, 247, 255));
rechts.add(refresh);
kopf.add(rechts, BorderLayout.EAST);
return kopf;
}
private JScrollPane erstelleTabelle() {
String[] spalten = {"Belegnummer", "Typ", "Kunde", "Datum", "Status", "Brutto"};
tabellenModell = new DefaultTableModel(spalten, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
tabelle = new JTable(tabellenModell);
tabelle.setRowHeight(28);
tabelle.setFont(new Font("Calibri", Font.PLAIN, 13));
tabelle.getTableHeader().setFont(new Font("Calibri", Font.BOLD, 13));
tabelle.getTableHeader().setBackground(new Color(30, 39, 97));
tabelle.getTableHeader().setForeground(Color.WHITE);
tabelle.setSelectionBackground(new Color(202, 220, 252));
tabelle.setGridColor(new Color(209, 217, 240));
ladeDokumente();
return new JScrollPane(tabelle);
}
private JPanel erstelleAktionen() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(202, 220, 252)),
BorderFactory.createEmptyBorder(10, 10, 10, 10)
));
JLabel aktLabel = new JLabel("Aktionen: ");
aktLabel.setFont(new Font("Calibri", Font.BOLD, 13));
panel.add(aktLabel);
JButton angebotBtn = erstelleButton("Neues Angebot", new Color(30, 39, 97));
angebotBtn.addActionListener(e -> neuesAngebotDialog());
panel.add(angebotBtn);
JButton rechnungBtn = erstelleButton("Rechnung aus Angebot", new Color(74, 144, 217));
rechnungBtn.addActionListener(e -> rechnungAusAngebotDialog());
panel.add(rechnungBtn);
JButton festschreibenBtn = erstelleButton("Rechnung festschreiben", new Color(100, 100, 100));
festschreibenBtn.addActionListener(e -> rechnungFestschreibenDialog());
panel.add(festschreibenBtn);
return panel;
}
private void neuesAngebotDialog() {
JDialog dialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(this),
"Neues Angebot erstellen", true);
dialog.setSize(400, 300);
dialog.setLocationRelativeTo(this);
dialog.setLayout(new BorderLayout(10, 10));
JPanel form = new JPanel(new GridLayout(0, 2, 5, 5));
form.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
form.add(new JLabel("Kunden-ID:"));
JTextField kundenIdField = new JTextField();
form.add(kundenIdField);
form.add(new JLabel("Produkt-ID:"));
JTextField produktIdField = new JTextField();
form.add(produktIdField);
form.add(new JLabel("Menge:"));
JTextField mengeField = new JTextField("1");
form.add(mengeField);
form.add(new JLabel("Gueltig bis (YYYY-MM-DD):"));
JTextField gueltigBisField = new JTextField(
LocalDate.now().plusDays(30).toString());
form.add(gueltigBisField);
dialog.add(form, BorderLayout.CENTER);
JButton erstellenBtn = erstelleButton("Angebot erstellen", new Color(30, 39, 97));
erstellenBtn.addActionListener(e -> {
try {
bestandsmanagement.ProduktServiceImpl ps =
new bestandsmanagement.ProduktServiceImpl();
bestandsmanagement.Produkt produkt =
ps.findeProdukt(produktIdField.getText());
if (produkt == null) {
JOptionPane.showMessageDialog(dialog, "Produkt nicht gefunden.");
return;
}
int menge = Integer.parseInt(mengeField.getText().trim());
List<Dokumentposition> positionen = new ArrayList<>();
positionen.add(new Dokumentposition(
produkt.getProduktId(), produkt.getBezeichnung(),
menge, produkt.getNettoEinzelpreis(), produkt.getMehrwertsteuersatz()
));
Angebot a = service.erstelleAngebot(kundenIdField.getText(),
positionen, LocalDate.parse(gueltigBisField.getText()));
if (a != null) {
JOptionPane.showMessageDialog(dialog,
"Angebot wurde erfolgreich erstellt: " + a.getBelegnummer(),
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
ladeDokumente();
dialog.dispose();
}
} catch (Exception ex) {
JOptionPane.showMessageDialog(dialog,
"Fehler: " + ex.getMessage(), "Fehler", JOptionPane.ERROR_MESSAGE);
}
});
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
btnPanel.add(erstellenBtn);
btnPanel.add(erstelleButton("Abbrechen", Color.GRAY));
dialog.add(btnPanel, BorderLayout.SOUTH);
dialog.setVisible(true);
}
private void rechnungAusAngebotDialog() {
String angebotsNr = JOptionPane.showInputDialog(this,
"Angebotsnummer eingeben:", "Rechnung aus Angebot",
JOptionPane.QUESTION_MESSAGE);
if (angebotsNr == null || angebotsNr.isBlank()) return;
Rechnung r = service.erstelleRechnungAusAngebot(
angebotsNr, LocalDate.now(), LocalDate.now(),
LocalDate.now().plusDays(14));
if (r != null) {
JOptionPane.showMessageDialog(this,
"Rechnung wurde erfolgreich erzeugt: " + r.getBelegnummer(),
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
ladeDokumente();
} else {
JOptionPane.showMessageDialog(this,
"Rechnung konnte nicht erstellt werden.",
"Fehler", JOptionPane.ERROR_MESSAGE);
}
}
private void rechnungFestschreibenDialog() {
String rechnungsNr = JOptionPane.showInputDialog(this,
"Rechnungsnummer eingeben:", "Rechnung festschreiben",
JOptionPane.QUESTION_MESSAGE);
if (rechnungsNr == null || rechnungsNr.isBlank()) return;
service.finalisiereRechnung(rechnungsNr);
ladeDokumente();
JOptionPane.showMessageDialog(this,
"Rechnung wurde festgeschrieben.",
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
}
private JButton erstelleButton(String text, Color farbe) {
JButton btn = new JButton(text);
btn.setBackground(farbe);
btn.setForeground(Color.WHITE);
btn.setFocusPainted(false);
btn.setBorderPainted(false);
btn.setFont(new Font("Calibri", Font.BOLD, 12));
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return btn;
}
private void ladeDokumente() {
tabellenModell.setRowCount(0);
if (service == null) return;
for (Angebot a : service.alleAngebote()) {
tabellenModell.addRow(new Object[]{
a.getBelegnummer(), "Angebot",
a.getKundenReferenz(), a.getDatum(),
a.getStatus(), String.format("%.2f EUR", a.getSummeBrutto())
});
}
for (Rechnung r : service.alleRechnungen()) {
tabellenModell.addRow(new Object[]{
r.getBelegnummer(), "Rechnung",
r.getKundenReferenz(), r.getDatum(),
r.isFestgeschrieben() ? "FESTGESCHRIEBEN" : r.getStatus(),
String.format("%.2f EUR", r.getSummeBrutto())
});
}
}
}

View File

@ -0,0 +1,215 @@
package ui;
import kundenverwaltung.Kunde;
import kundenverwaltung.KundenServiceImpl;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.io.IOException;
import java.util.List;
public class KundenPanel extends JPanel {
private KundenServiceImpl service;
private JTable tabelle;
private DefaultTableModel tabellenModell;
private JTextField suchfeld;
public KundenPanel() {
try { service = new KundenServiceImpl(); }
catch (IOException e) { e.printStackTrace(); }
setLayout(new BorderLayout(10, 10));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
setBackground(new Color(244, 247, 255));
add(erstelleKopf(), BorderLayout.NORTH);
add(erstelleTabelle(), BorderLayout.CENTER);
add(erstelleFormular(), BorderLayout.EAST);
add(erstelleAktionen(), BorderLayout.SOUTH);
}
private JPanel erstelleKopf() {
JPanel kopf = new JPanel(new BorderLayout());
kopf.setBackground(new Color(244, 247, 255));
JLabel titel = new JLabel("Kundenverwaltung");
titel.setFont(new Font("Cambria", Font.BOLD, 20));
titel.setForeground(new Color(30, 39, 97));
kopf.add(titel, BorderLayout.WEST);
JPanel suchPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
suchPanel.setBackground(new Color(244, 247, 255));
suchfeld = new JTextField(15);
JButton suchenBtn = erstelleButton("Suchen", new Color(74, 144, 217));
suchenBtn.addActionListener(e -> sucheKunden());
JButton alleBtn = erstelleButton("Alle anzeigen", new Color(30, 39, 97));
alleBtn.addActionListener(e -> ladeKunden());
suchPanel.add(new JLabel("Suche: "));
suchPanel.add(suchfeld);
suchPanel.add(suchenBtn);
suchPanel.add(alleBtn);
kopf.add(suchPanel, BorderLayout.EAST);
return kopf;
}
private JScrollPane erstelleTabelle() {
String[] spalten = {"Kunden-ID", "Name", "PLZ", "Ort", "E-Mail", "Telefon"};
tabellenModell = new DefaultTableModel(spalten, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
tabelle = new JTable(tabellenModell);
tabelle.setRowHeight(28);
tabelle.setFont(new Font("Calibri", Font.PLAIN, 13));
tabelle.getTableHeader().setFont(new Font("Calibri", Font.BOLD, 13));
tabelle.getTableHeader().setBackground(new Color(30, 39, 97));
tabelle.getTableHeader().setForeground(Color.WHITE);
tabelle.setSelectionBackground(new Color(202, 220, 252));
tabelle.setGridColor(new Color(209, 217, 240));
ladeKunden();
return new JScrollPane(tabelle);
}
private JPanel erstelleFormular() {
JPanel form = new JPanel();
form.setLayout(new BoxLayout(form, BoxLayout.Y_AXIS));
form.setBackground(Color.WHITE);
form.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(202, 220, 252)),
BorderFactory.createEmptyBorder(15, 15, 15, 15)
));
form.setPreferredSize(new Dimension(280, 0));
JLabel formTitel = new JLabel("Neuen Kunden anlegen");
formTitel.setFont(new Font("Cambria", Font.BOLD, 14));
formTitel.setForeground(new Color(30, 39, 97));
formTitel.setAlignmentX(Component.LEFT_ALIGNMENT);
form.add(formTitel);
form.add(Box.createVerticalStrut(15));
JTextField nameField = erstelleFeld(form, "Name / Firmenname *");
JTextField strasseField = erstelleFeld(form, "Strasse *");
JTextField plzField = erstelleFeld(form, "PLZ *");
JTextField ortField = erstelleFeld(form, "Ort *");
JTextField steuerField = erstelleFeld(form, "Steuernummer *");
JTextField emailField = erstelleFeld(form, "E-Mail *");
JTextField telefonField = erstelleFeld(form, "Telefon *");
form.add(Box.createVerticalStrut(15));
JButton speichernBtn = erstelleButton("Speichern", new Color(30, 39, 97));
speichernBtn.setAlignmentX(Component.LEFT_ALIGNMENT);
speichernBtn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35));
speichernBtn.addActionListener(e -> {
if (nameField.getText().isBlank() || strasseField.getText().isBlank() ||
plzField.getText().isBlank() || ortField.getText().isBlank() ||
steuerField.getText().isBlank() || emailField.getText().isBlank() ||
telefonField.getText().isBlank()) {
JOptionPane.showMessageDialog(this,
"Bitte füllen Sie alle Pflichtfelder aus.",
"Pflichtfeld fehlt", JOptionPane.WARNING_MESSAGE);
return;
}
service.kundeAnlegen(nameField.getText(), strasseField.getText(),
plzField.getText(), ortField.getText(), steuerField.getText(),
emailField.getText(), telefonField.getText());
nameField.setText(""); strasseField.setText(""); plzField.setText("");
ortField.setText(""); steuerField.setText(""); emailField.setText("");
telefonField.setText("");
ladeKunden();
JOptionPane.showMessageDialog(this, "Kunde wurde erfolgreich gespeichert.",
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
});
form.add(speichernBtn);
return form;
}
private JPanel erstelleAktionen() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(202, 220, 252)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
JButton loeschenBtn = erstelleButton("Ausgewaehlten Kunden loeschen", new Color(180, 50, 50));
loeschenBtn.addActionListener(e -> {
int zeile = tabelle.getSelectedRow();
if (zeile == -1) {
JOptionPane.showMessageDialog(this,
"Bitte waehlen Sie einen Kunden aus.",
"Kein Kunde ausgewaehlt", JOptionPane.WARNING_MESSAGE);
return;
}
String kundenId = (String) tabellenModell.getValueAt(zeile, 0);
int bestaetigung = JOptionPane.showConfirmDialog(this,
"Moechten Sie den Kunden " + kundenId + " wirklich loeschen?",
"Loeschen bestaetigen", JOptionPane.YES_NO_OPTION);
if (bestaetigung == JOptionPane.YES_OPTION) {
boolean ok = service.kundeLoschen(kundenId);
if (ok) {
ladeKunden();
JOptionPane.showMessageDialog(this,
"Kunde wurde erfolgreich geloescht.",
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(this,
"Kunde kann nicht geloescht werden, da er in einem Beleg referenziert wird.",
"Loeschen nicht moeglich", JOptionPane.ERROR_MESSAGE);
}
}
});
panel.add(loeschenBtn);
return panel;
}
private JTextField erstelleFeld(JPanel panel, String label) {
JLabel lbl = new JLabel(label);
lbl.setFont(new Font("Calibri", Font.PLAIN, 12));
lbl.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(lbl);
panel.add(Box.createVerticalStrut(3));
JTextField feld = new JTextField();
feld.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
feld.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(feld);
panel.add(Box.createVerticalStrut(8));
return feld;
}
private JButton erstelleButton(String text, Color farbe) {
JButton btn = new JButton(text);
btn.setBackground(farbe);
btn.setForeground(Color.WHITE);
btn.setFocusPainted(false);
btn.setBorderPainted(false);
btn.setFont(new Font("Calibri", Font.BOLD, 12));
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return btn;
}
private void ladeKunden() {
tabellenModell.setRowCount(0);
if (service == null) return;
List<Kunde> kunden = service.alleKunden();
for (Kunde k : kunden) {
tabellenModell.addRow(new Object[]{
k.getKundenId(), k.getName(), k.getPlz(),
k.getOrt(), k.getEmail(), k.getTelefon()
});
}
}
private void sucheKunden() {
tabellenModell.setRowCount(0);
if (service == null) return;
List<Kunde> kunden = service.sucheNachName(suchfeld.getText());
for (Kunde k : kunden) {
tabellenModell.addRow(new Object[]{
k.getKundenId(), k.getName(), k.getPlz(),
k.getOrt(), k.getEmail(), k.getTelefon()
});
}
}
}

View File

@ -0,0 +1,99 @@
package ui;
import javax.swing.*;
import java.awt.*;
public class MainWindow extends JFrame {
private JPanel contentPanel;
private KundenPanel kundenPanel;
private ProduktPanel produktPanel;
private DokumentePanel dokumentePanel;
public MainWindow() {
setTitle("Fakturierungsanwendung Team 3");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000, 650);
setLocationRelativeTo(null);
setLayout(new BorderLayout());
// Navigation oben
add(erstelleNavigation(), BorderLayout.NORTH);
// Content Bereich
contentPanel = new JPanel(new CardLayout());
kundenPanel = new KundenPanel();
produktPanel = new ProduktPanel();
dokumentePanel = new DokumentePanel();
contentPanel.add(kundenPanel, "KUNDEN");
contentPanel.add(produktPanel, "PRODUKTE");
contentPanel.add(dokumentePanel, "DOKUMENTE");
add(contentPanel, BorderLayout.CENTER);
// Statusleiste unten
JLabel status = new JLabel(" Fakturierungsanwendung | Team 3 | Gruppe J, K, L, I");
status.setBorder(BorderFactory.createEtchedBorder());
status.setFont(new Font("Calibri", Font.PLAIN, 11));
status.setForeground(Color.GRAY);
add(status, BorderLayout.SOUTH);
zeigePanel("KUNDEN");
}
private JPanel erstelleNavigation() {
JPanel nav = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
nav.setBackground(new Color(30, 39, 97));
nav.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10));
JLabel logo = new JLabel(" Fakturierung ");
logo.setFont(new Font("Cambria", Font.BOLD, 16));
logo.setForeground(Color.WHITE);
nav.add(logo);
nav.add(Box.createHorizontalStrut(20));
nav.add(erstelleNavButton("Kunden", "KUNDEN"));
nav.add(erstelleNavButton("Produkte", "PRODUKTE"));
nav.add(erstelleNavButton("Dokumente", "DOKUMENTE"));
return nav;
}
private JButton erstelleNavButton(String text, String panel) {
JButton btn = new JButton(text);
btn.setFont(new Font("Calibri", Font.PLAIN, 13));
btn.setForeground(Color.WHITE);
btn.setBackground(new Color(30, 39, 97));
btn.setBorderPainted(false);
btn.setFocusPainted(false);
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
btn.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15));
btn.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent e) {
btn.setBackground(new Color(74, 144, 217));
}
public void mouseExited(java.awt.event.MouseEvent e) {
btn.setBackground(new Color(30, 39, 97));
}
});
btn.addActionListener(e -> zeigePanel(panel));
return btn;
}
private void zeigePanel(String name) {
CardLayout cl = (CardLayout) contentPanel.getLayout();
cl.show(contentPanel, name);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
com.formdev.flatlaf.FlatLightLaf.setup();
} catch (Exception ignored) {}
new MainWindow().setVisible(true);
});
}
}

View File

@ -0,0 +1,226 @@
package ui;
import bestandsmanagement.Produkt;
import bestandsmanagement.ProduktServiceImpl;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
public class ProduktPanel extends JPanel {
private ProduktServiceImpl service;
private JTable tabelle;
private DefaultTableModel tabellenModell;
private JTextField suchfeld;
public ProduktPanel() {
try { service = new ProduktServiceImpl(); }
catch (IOException e) { e.printStackTrace(); }
setLayout(new BorderLayout(10, 10));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
setBackground(new Color(244, 247, 255));
add(erstelleKopf(), BorderLayout.NORTH);
add(erstelleTabelle(), BorderLayout.CENTER);
add(erstelleFormular(), BorderLayout.EAST);
add(erstelleAktionen(), BorderLayout.SOUTH);
}
private JPanel erstelleKopf() {
JPanel kopf = new JPanel(new BorderLayout());
kopf.setBackground(new Color(244, 247, 255));
JLabel titel = new JLabel("Bestandsmanagement");
titel.setFont(new Font("Cambria", Font.BOLD, 20));
titel.setForeground(new Color(30, 39, 97));
kopf.add(titel, BorderLayout.WEST);
JPanel suchPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
suchPanel.setBackground(new Color(244, 247, 255));
suchfeld = new JTextField(15);
JButton suchenBtn = erstelleButton("Suchen", new Color(74, 144, 217));
suchenBtn.addActionListener(e -> sucheProdukte());
JButton alleBtn = erstelleButton("Alle anzeigen", new Color(30, 39, 97));
alleBtn.addActionListener(e -> ladeProdukte());
suchPanel.add(new JLabel("Suche: "));
suchPanel.add(suchfeld);
suchPanel.add(suchenBtn);
suchPanel.add(alleBtn);
kopf.add(suchPanel, BorderLayout.EAST);
return kopf;
}
private JScrollPane erstelleTabelle() {
String[] spalten = {"Produkt-ID", "Bezeichnung", "Preis (Netto)", "MwSt", "Bestand", "Status"};
tabellenModell = new DefaultTableModel(spalten, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
tabelle = new JTable(tabellenModell);
tabelle.setRowHeight(28);
tabelle.setFont(new Font("Calibri", Font.PLAIN, 13));
tabelle.getTableHeader().setFont(new Font("Calibri", Font.BOLD, 13));
tabelle.getTableHeader().setBackground(new Color(30, 39, 97));
tabelle.getTableHeader().setForeground(Color.WHITE);
tabelle.setSelectionBackground(new Color(202, 220, 252));
tabelle.setGridColor(new Color(209, 217, 240));
ladeProdukte();
return new JScrollPane(tabelle);
}
private JPanel erstelleFormular() {
JPanel form = new JPanel();
form.setLayout(new BoxLayout(form, BoxLayout.Y_AXIS));
form.setBackground(Color.WHITE);
form.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(202, 220, 252)),
BorderFactory.createEmptyBorder(15, 15, 15, 15)
));
form.setPreferredSize(new Dimension(280, 0));
JLabel formTitel = new JLabel("Neuen Artikel anlegen");
formTitel.setFont(new Font("Cambria", Font.BOLD, 14));
formTitel.setForeground(new Color(30, 39, 97));
formTitel.setAlignmentX(Component.LEFT_ALIGNMENT);
form.add(formTitel);
form.add(Box.createVerticalStrut(15));
JTextField bezeichnungField = erstelleFeld(form, "Bezeichnung *");
JTextField preisField = erstelleFeld(form, "Netto-Preis (z.B. 19.99) *");
JTextField mwstField = erstelleFeld(form, "MwSt-Satz (z.B. 0.19) *");
JTextField bestandField = erstelleFeld(form, "Bestand *");
JTextField beschField = erstelleFeld(form, "Beschreibung (optional)");
form.add(Box.createVerticalStrut(15));
JButton speichernBtn = erstelleButton("Speichern", new Color(30, 39, 97));
speichernBtn.setAlignmentX(Component.LEFT_ALIGNMENT);
speichernBtn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35));
speichernBtn.addActionListener(e -> {
try {
if (bezeichnungField.getText().isBlank() || preisField.getText().isBlank() ||
mwstField.getText().isBlank() || bestandField.getText().isBlank()) {
JOptionPane.showMessageDialog(this,
"Bitte füllen Sie alle Pflichtfelder aus.",
"Pflichtfeld fehlt", JOptionPane.WARNING_MESSAGE);
return;
}
BigDecimal preis = new BigDecimal(preisField.getText().replace(",", "."));
BigDecimal mwst = new BigDecimal(mwstField.getText().replace(",", "."));
int bestand = Integer.parseInt(bestandField.getText().trim());
String beschreibung = beschField.getText().isBlank() ? null : beschField.getText();
service.produktAnlegen(bezeichnungField.getText(), preis, mwst, bestand, beschreibung);
bezeichnungField.setText(""); preisField.setText(""); mwstField.setText("");
bestandField.setText(""); beschField.setText("");
ladeProdukte();
JOptionPane.showMessageDialog(this, "Produkt wurde erfolgreich gespeichert.",
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this,
"Bitte gültige Zahlen für Preis, MwSt und Bestand eingeben.",
"Ungültige Eingabe", JOptionPane.ERROR_MESSAGE);
}
});
form.add(speichernBtn);
return form;
}
private JPanel erstelleAktionen() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(202, 220, 252)),
BorderFactory.createEmptyBorder(5, 10, 5, 10)
));
JButton loeschenBtn = erstelleButton("Ausgewaehlten Artikel loeschen", new Color(180, 50, 50));
loeschenBtn.addActionListener(e -> {
int zeile = tabelle.getSelectedRow();
if (zeile == -1) {
JOptionPane.showMessageDialog(this,
"Bitte waehlen Sie einen Artikel aus.",
"Kein Artikel ausgewaehlt", JOptionPane.WARNING_MESSAGE);
return;
}
String produktId = (String) tabellenModell.getValueAt(zeile, 0);
int bestaetigung = JOptionPane.showConfirmDialog(this,
"Moechten Sie den Artikel " + produktId + " wirklich loeschen?",
"Loeschen bestaetigen", JOptionPane.YES_NO_OPTION);
if (bestaetigung == JOptionPane.YES_OPTION) {
boolean ok = service.produktLoeschen(produktId);
if (ok) {
ladeProdukte();
JOptionPane.showMessageDialog(this,
"Artikel wurde erfolgreich geloescht.",
"Erfolg", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(this,
"Artikel kann nicht geloescht werden, da er in einem Beleg referenziert wird.",
"Loeschen nicht moeglich", JOptionPane.ERROR_MESSAGE);
}
}
});
panel.add(loeschenBtn);
return panel;
}
private JTextField erstelleFeld(JPanel panel, String label) {
JLabel lbl = new JLabel(label);
lbl.setFont(new Font("Calibri", Font.PLAIN, 12));
lbl.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(lbl);
panel.add(Box.createVerticalStrut(3));
JTextField feld = new JTextField();
feld.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
feld.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(feld);
panel.add(Box.createVerticalStrut(8));
return feld;
}
private JButton erstelleButton(String text, Color farbe) {
JButton btn = new JButton(text);
btn.setBackground(farbe);
btn.setForeground(Color.WHITE);
btn.setFocusPainted(false);
btn.setBorderPainted(false);
btn.setFont(new Font("Calibri", Font.BOLD, 12));
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return btn;
}
private void ladeProdukte() {
tabellenModell.setRowCount(0);
if (service == null) return;
List<Produkt> produkte = service.alleProdukte();
for (Produkt p : produkte) {
tabellenModell.addRow(new Object[]{
p.getProduktId(), p.getBezeichnung(),
String.format("%.2f EUR", p.getNettoEinzelpreis()),
String.format("%.0f%%", p.getMehrwertsteuersatz().multiply(new BigDecimal("100"))),
p.getBestand(),
p.isVerfuegbar() ? "VERFUEGBAR" : "NICHT VERFUEGBAR"
});
}
}
private void sucheProdukte() {
tabellenModell.setRowCount(0);
if (service == null) return;
List<Produkt> produkte = service.sucheNachBezeichnung(suchfeld.getText());
for (Produkt p : produkte) {
tabellenModell.addRow(new Object[]{
p.getProduktId(), p.getBezeichnung(),
String.format("%.2f EUR", p.getNettoEinzelpreis()),
String.format("%.0f%%", p.getMehrwertsteuersatz().multiply(new BigDecimal("100"))),
p.getBestand(),
p.isVerfuegbar() ? "VERFUEGBAR" : "NICHT VERFUEGBAR"
});
}
}
}

View File

@ -0,0 +1,145 @@
package belegworkflow;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class BelegworkflowTest {
private BelegworkflowService service;
private List<Dokumentposition> testPositionen;
@BeforeEach
void setUp() throws Exception {
service = new BelegworkflowService();
testPositionen = List.of(new Dokumentposition(
"P-000001", "Testprodukt",
2, new BigDecimal("10.00"), new BigDecimal("0.19")
));
}
@Test
@DisplayName("TC-01: Angebot erstellen -> Angebotsnummer AN- vergeben (F-01, F-02)")
void tc01AngebotErstellen() {
Angebot a = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a);
assertTrue(a.getBelegnummer().startsWith("AN-"));
assertEquals(AngebotStatus.OFFEN, a.getStatus());
}
@Test
@DisplayName("TC-02: Angebot ohne Kunde wird abgelehnt (F-01)")
void tc02AngebotOhneKunde() {
Angebot a = service.erstelleAngebot("", testPositionen,
LocalDate.now().plusDays(30));
assertNull(a);
}
@Test
@DisplayName("TC-03: Angebot ohne Positionen wird abgelehnt (F-01)")
void tc03AngebotOhnePositionen() {
Angebot a = service.erstelleAngebot("K-000001", List.of(),
LocalDate.now().plusDays(30));
assertNull(a);
}
@Test
@DisplayName("TC-04: Summenberechnung korrekt (F-03, GR-06)")
void tc04Summenberechnung() {
Angebot a = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a);
assertEquals(new BigDecimal("20.00"), a.getSummeNetto());
assertEquals(new BigDecimal("3.80"), a.getSummeSteuer());
assertEquals(new BigDecimal("23.80"), a.getSummeBrutto());
}
@Test
@DisplayName("TC-05: Rechnung aus Angebot -> Angebot wird auf UEBERFUEHRT gesetzt (F-05)")
void tc05RechnungAusAngebot() {
Angebot a = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a);
Rechnung r = service.erstelleRechnungAusAngebot(a.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
assertNotNull(r);
assertTrue(r.getBelegnummer().startsWith("R-"));
assertEquals(AngebotStatus.UEBERFUEHRT, a.getStatus());
}
@Test
@DisplayName("TC-06: Rechnungsnummer ist fortlaufend (F-07, GR-01)")
void tc06RechnungsnummerFortlaufend() {
Angebot a1 = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
Angebot a2 = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a1);
assertNotNull(a2);
Rechnung r1 = service.erstelleRechnungAusAngebot(a1.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
Rechnung r2 = service.erstelleRechnungAusAngebot(a2.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
assertNotNull(r1);
assertNotNull(r2);
assertNotEquals(r1.getBelegnummer(), r2.getBelegnummer());
}
@Test
@DisplayName("TC-07: Rechnung aus ueberführtem Angebot wird abgelehnt (F-05)")
void tc07RechnungAusUeberfuehrtemAngebot() {
Angebot a = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a);
service.erstelleRechnungAusAngebot(a.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
Rechnung r2 = service.erstelleRechnungAusAngebot(a.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
assertNull(r2);
}
@Test
@DisplayName("TC-08: Festgeschriebene Rechnung kann nicht geaendert werden (F-10)")
void tc08FestgeschriebeneRechnung() {
Angebot a = service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertNotNull(a);
Rechnung r = service.erstelleRechnungAusAngebot(a.getBelegnummer(),
LocalDate.now(), LocalDate.now(), LocalDate.now().plusDays(14));
assertNotNull(r);
service.finalisiereRechnung(r.getBelegnummer());
assertTrue(r.isFestgeschrieben());
r.setStatus(RechnungStatus.BEZAHLT);
assertEquals(RechnungStatus.OFFEN, r.getStatus());
}
@Test
@DisplayName("TC-09: Kunde ist referenziert wird erkannt (F-12)")
void tc09KundeReferenziert() {
service.erstelleAngebot("K-000099", testPositionen,
LocalDate.now().plusDays(30));
assertTrue(service.istKundeReferenziert("K-000099"));
assertFalse(service.istKundeReferenziert("K-999999"));
}
@Test
@DisplayName("TC-10: Produkt ist referenziert wird erkannt (F-12)")
void tc10ProduktReferenziert() {
service.erstelleAngebot("K-000001", testPositionen,
LocalDate.now().plusDays(30));
assertTrue(service.istProduktReferenziert("P-000001"));
assertFalse(service.istProduktReferenziert("P-999999"));
}
}

View File

@ -0,0 +1,114 @@
package bestandsmanagement;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class BestandsmanagementTest {
private ProduktServiceImpl service;
@BeforeEach
void setUp() throws Exception {
service = new ProduktServiceImpl();
}
@Test
@DisplayName("TC-01: Artikel anlegen -> ID P-000001 vergeben")
void tc01ArtikelAnlegen() {
Produkt p = service.produktAnlegen("Testprodukt",
new BigDecimal("19.99"), new BigDecimal("0.19"), 100, null);
assertNotNull(p);
assertTrue(p.getProduktId().startsWith("P-"));
}
@Test
@DisplayName("TC-02: Artikel mit negativem Bestand wird abgelehnt (F-09)")
void tc02NegativerBestand() {
Produkt p = service.produktAnlegen("Testprodukt",
new BigDecimal("19.99"), new BigDecimal("0.19"), -1, null);
assertNull(p);
}
@Test
@DisplayName("TC-03: Artikel ohne Bezeichnung wird abgelehnt")
void tc03FehlenderName() {
Produkt p = service.produktAnlegen("",
new BigDecimal("19.99"), new BigDecimal("0.19"), 10, null);
assertNull(p);
}
@Test
@DisplayName("TC-04: Bestand 0 -> Artikel wird als nicht verfuegbar markiert (F-08)")
void tc04BestandNull() {
Produkt p = service.produktAnlegen("Ausverkauft",
new BigDecimal("9.99"), new BigDecimal("0.19"), 0, null);
assertNotNull(p);
assertFalse(p.isVerfuegbar());
}
@Test
@DisplayName("TC-05: Bestand > 0 -> Artikel ist verfuegbar")
void tc05BestandPositiv() {
Produkt p = service.produktAnlegen("Verfuegbar",
new BigDecimal("9.99"), new BigDecimal("0.19"), 5, null);
assertNotNull(p);
assertTrue(p.isVerfuegbar());
}
@Test
@DisplayName("TC-06: Artikel bearbeiten aktualisiert Preis (F-03, F-04)")
void tc06ArtikelBearbeiten() {
Produkt p = service.produktAnlegen("Edit Artikel",
new BigDecimal("10.00"), new BigDecimal("0.19"), 10, null);
assertNotNull(p);
boolean ok = service.produktBearbeiten(p.getProduktId(), "Edit Artikel",
new BigDecimal("15.00"), new BigDecimal("0.19"), 10, null);
assertTrue(ok);
assertEquals(new BigDecimal("15.00"),
service.findeProdukt(p.getProduktId()).getNettoEinzelpreis());
}
@Test
@DisplayName("TC-07: Suche nach Bezeichnung findet Treffer (F-07)")
void tc07SucheNachBezeichnung() {
service.produktAnlegen("Hammer",
new BigDecimal("5.99"), new BigDecimal("0.19"), 20, null);
List<Produkt> treffer = service.sucheNachBezeichnung("Hammer");
assertFalse(treffer.isEmpty());
assertEquals("Hammer", treffer.get(0).getBezeichnung());
}
@Test
@DisplayName("TC-08: findeProdukt gibt null fuer unbekannte ID zurueck")
void tc08FindeProduktNull() {
assertNull(service.findeProdukt("P-999999"));
}
@Test
@DisplayName("TC-09: Loeschen referenzierter Artikel wird abgelehnt (F-11)")
void tc09LoeschsperreReferenzierterArtikel() {
Produkt p = service.produktAnlegen("Ref Artikel",
new BigDecimal("1.00"), new BigDecimal("0.19"), 5, null);
assertNotNull(p);
p.belegHinzufuegen("R-2026-000001");
boolean geloescht = service.produktLoeschen(p.getProduktId());
assertFalse(geloescht);
}
@Test
@DisplayName("TC-10: Loeschen nicht referenzierter Artikel funktioniert (F-10)")
void tc10LoeschenErfolgreich() {
Produkt p = service.produktAnlegen("Temp Artikel",
new BigDecimal("1.00"), new BigDecimal("0.19"), 5, null);
assertNotNull(p);
boolean geloescht = service.produktLoeschen(p.getProduktId());
assertTrue(geloescht);
assertNull(service.findeProdukt(p.getProduktId()));
}
}

View File

@ -3,9 +3,7 @@ package kundenverwaltung;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -20,7 +18,7 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-01: Kunde anlegen -> ID K-000001 vergeben") @DisplayName("TC-01: Kunde anlegen -> ID K- vergeben (F-01, F-02)")
void tc01KundeAnlegen() { void tc01KundeAnlegen() {
Kunde k = service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163", Kunde k = service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163",
"Mannheim", "DE123456789", "info@muster.de", "0621-123456"); "Mannheim", "DE123456789", "info@muster.de", "0621-123456");
@ -30,15 +28,24 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-03: Speichern ohne E-Mail wird abgelehnt") @DisplayName("TC-02: Fortlaufende ID mit fuehrenden Nullen (F-02)")
void tc03FehlendePflichtfeld() { void tc02KundenIdFormat() {
Kunde k = service.kundeAnlegen("Format GmbH", "Str. 1", "68163",
"Mannheim", "DE000000001", "format@test.de", "0621-000001");
assertNotNull(k);
assertTrue(k.getKundenId().matches("K-\\d{6}"));
}
@Test
@DisplayName("TC-03: Speichern ohne E-Mail wird abgelehnt (F-03)")
void tc03FehlendeEmail() {
Kunde k = service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163", Kunde k = service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163",
"Mannheim", "DE123456789", "", "0621-123456"); "Mannheim", "DE123456789", "", "0621-123456");
assertNull(k); assertNull(k);
} }
@Test @Test
@DisplayName("TC-04: Speichern ohne Name wird abgelehnt") @DisplayName("TC-04: Speichern ohne Name wird abgelehnt (F-03)")
void tc04FehlenderName() { void tc04FehlenderName() {
Kunde k = service.kundeAnlegen("", "Hauptstr. 1", "68163", Kunde k = service.kundeAnlegen("", "Hauptstr. 1", "68163",
"Mannheim", "DE123456789", "info@muster.de", "0621-123456"); "Mannheim", "DE123456789", "info@muster.de", "0621-123456");
@ -46,7 +53,34 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-07: Suche nach Name findet Treffer") @DisplayName("TC-05: Datenpersistenz - Daten nach Neustart erhalten (F-04, NF-SEC-01)")
void tc05Datenpersistenz() throws Exception {
Kunde k = service.kundeAnlegen("Persistenz GmbH", "Str. 1", "68163",
"Mannheim", "DE555555555", "persist@test.de", "0621-555555");
assertNotNull(k);
String id = k.getKundenId();
// Neuen Service erstellen simuliert Neustart
KundenServiceImpl neuerService = new KundenServiceImpl();
Kunde geladen = neuerService.findeKunde(id);
assertNotNull(geladen);
assertEquals("Persistenz GmbH", geladen.getName());
}
@Test
@DisplayName("TC-06: Kunde bearbeiten aktualisiert E-Mail (F-05, F-06, F-07)")
void tc06KundeBearbeiten() {
Kunde k = service.kundeAnlegen("Edit GmbH", "Str. 1", "68163",
"Mannheim", "DE333333333", "alt@edit.de", "0621-333333");
assertNotNull(k);
boolean ok = service.kundeBearbeiten(k.getKundenId(), "Edit GmbH",
"Str. 1", "68163", "Mannheim", "DE333333333", "neu@edit.de", "0621-333333");
assertTrue(ok);
assertEquals("neu@edit.de", service.findeKunde(k.getKundenId()).getEmail());
}
@Test
@DisplayName("TC-07: Suche nach Name findet Treffer (F-09)")
void tc07SucheNachName() { void tc07SucheNachName() {
service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163", service.kundeAnlegen("Muster GmbH", "Hauptstr. 1", "68163",
"Mannheim", "DE123456789", "info@muster.de", "0621-123456"); "Mannheim", "DE123456789", "info@muster.de", "0621-123456");
@ -56,7 +90,7 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-08: findeKunde gibt Objekt zurueck") @DisplayName("TC-08: findeKunde gibt Objekt zurueck (F-10)")
void tc08FindeKunde() { void tc08FindeKunde() {
Kunde angelegt = service.kundeAnlegen("Test AG", "Teststr. 1", "68163", Kunde angelegt = service.kundeAnlegen("Test AG", "Teststr. 1", "68163",
"Mannheim", "DE987654321", "test@test.de", "0621-654321"); "Mannheim", "DE987654321", "test@test.de", "0621-654321");
@ -67,25 +101,32 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-09: findeKunde gibt null zurueck fuer unbekannte ID") @DisplayName("TC-09: findeKunde gibt null zurueck fuer unbekannte ID (F-10)")
void tc09FindeKundeNull() { void tc09FindeKundeNull() {
Kunde k = service.findeKunde("K-999999"); assertNull(service.findeKunde("K-999999"));
assertNull(k);
} }
@Test @Test
@DisplayName("TC-10: Loeschen referenzierter Kunde wird abgelehnt") @DisplayName("TC-10: Loeschen referenzierter Kunde wird abgelehnt (F-12, GR-04)")
void tc10LoeschsperreReferenzierterKunde() { void tc10LoeschsperreReferenzierterKunde() throws Exception {
Kunde k = service.kundeAnlegen("Ref GmbH", "Str. 1", "68163", Kunde k = service.kundeAnlegen("Ref GmbH", "Str. 1", "68163",
"Mannheim", "DE111111111", "ref@ref.de", "0621-111111"); "Mannheim", "DE111111111", "ref@ref.de", "0621-111111");
assertNotNull(k); assertNotNull(k);
k.belegHinzufuegen("R-2026-000001");
java.nio.file.Files.createDirectories(java.nio.file.Path.of("data"));
java.nio.file.Files.writeString(
java.nio.file.Path.of("data/angebote.csv"),
"AN-2026-000001;2026-06-24;" + k.getKundenId() + ";2026-07-31;OFFEN;100.00;19.00;119.00\n"
);
boolean geloescht = service.kundeLoschen(k.getKundenId()); boolean geloescht = service.kundeLoschen(k.getKundenId());
assertFalse(geloescht); assertFalse(geloescht);
java.nio.file.Files.deleteIfExists(java.nio.file.Path.of("data/angebote.csv"));
} }
@Test @Test
@DisplayName("TC-11: Loeschen nicht referenzierter Kunde funktioniert") @DisplayName("TC-11: Loeschen nicht referenzierter Kunde funktioniert (F-11)")
void tc11LoeschenErfolgreich() { void tc11LoeschenErfolgreich() {
Kunde k = service.kundeAnlegen("Temp GmbH", "Str. 1", "68163", Kunde k = service.kundeAnlegen("Temp GmbH", "Str. 1", "68163",
"Mannheim", "DE222222222", "temp@temp.de", "0621-222222"); "Mannheim", "DE222222222", "temp@temp.de", "0621-222222");
@ -96,14 +137,30 @@ class KundenVerwaltungTest {
} }
@Test @Test
@DisplayName("TC-06: Kunde bearbeiten aktualisiert E-Mail") @DisplayName("TC-12: Transaktionshistorie zeigt verknuepfte Belege (F-13)")
void tc06KundeBearbeiten() { void tc12Transaktionshistorie() {
Kunde k = service.kundeAnlegen("Edit GmbH", "Str. 1", "68163", Kunde k = service.kundeAnlegen("Historie GmbH", "Str. 1", "68163",
"Mannheim", "DE333333333", "alt@edit.de", "0621-333333"); "Mannheim", "DE444444444", "hist@test.de", "0621-444444");
assertNotNull(k); assertNotNull(k);
boolean ok = service.kundeBearbeiten(k.getKundenId(), "Edit GmbH", k.belegHinzufuegen("AN-2026-000001");
"Str. 1", "68163", "Mannheim", "DE333333333", "neu@edit.de", "0621-333333"); k.belegHinzufuegen("R-2026-000001");
assertTrue(ok); assertEquals(2, k.getBelegReferenzen().size());
assertEquals("neu@edit.de", service.findeKunde(k.getKundenId()).getEmail()); assertTrue(k.getBelegReferenzen().contains("AN-2026-000001"));
assertTrue(k.getBelegReferenzen().contains("R-2026-000001"));
}
@Test
@DisplayName("TC-13: Suche liefert Ergebnis in unter 1 Sekunde (NF-PERF-01)")
void tc13Performance() throws Exception {
for (int i = 0; i < 100; i++) {
service.kundeAnlegen("Kunde " + i, "Str. " + i, "68163",
"Mannheim", "DE" + String.format("%09d", i),
"kunde" + i + "@test.de", "0621-" + String.format("%06d", i));
}
long start = System.currentTimeMillis();
List<Kunde> treffer = service.sucheNachName("Kunde");
long dauer = System.currentTimeMillis() - start;
assertFalse(treffer.isEmpty());
assertTrue(dauer < 1000, "Suche dauerte laenger als 1 Sekunde: " + dauer + "ms");
} }
} }