Observer Patter, Flatlaf, Persistente Daten
parent
082fee2923
commit
26c3840d07
7
pom.xml
7
pom.xml
|
|
@ -44,6 +44,13 @@
|
||||||
<version>${pdfbox.version}</version>
|
<version>${pdfbox.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Modernes Swing-Look-and-Feel (Q-05: Bedienbarkeit) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.formdev</groupId>
|
||||||
|
<artifactId>flatlaf</artifactId>
|
||||||
|
<version>3.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Modultests (JUnit 5, Kapitel 10 der Pflichtenhefte) -->
|
<!-- Modultests (JUnit 5, Kapitel 10 der Pflichtenhefte) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import de.team1.faktura.dokumente.EinfacherBelegnummernGenerator;
|
||||||
import de.team1.faktura.dokumente.JsonDokumentRepository;
|
import de.team1.faktura.dokumente.JsonDokumentRepository;
|
||||||
import de.team1.faktura.dokumente.PdfBoxPdfExporter;
|
import de.team1.faktura.dokumente.PdfBoxPdfExporter;
|
||||||
import de.team1.faktura.dokumente.StandardDokumentService;
|
import de.team1.faktura.dokumente.StandardDokumentService;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.gui.DokumentListenPanel;
|
import de.team1.faktura.gui.DokumentListenPanel;
|
||||||
import de.team1.faktura.gui.HauptFenster;
|
import de.team1.faktura.gui.HauptFenster;
|
||||||
import de.team1.faktura.gui.KundenPanel;
|
import de.team1.faktura.gui.KundenPanel;
|
||||||
|
|
@ -20,6 +21,8 @@ import de.team1.faktura.produkte.JsonProduktRepository;
|
||||||
import de.team1.faktura.produkte.ProduktCsvExport;
|
import de.team1.faktura.produkte.ProduktCsvExport;
|
||||||
import de.team1.faktura.produkte.ProduktVerwaltungsService;
|
import de.team1.faktura.produkte.ProduktVerwaltungsService;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
@ -49,17 +52,22 @@ public final class Main {
|
||||||
// Gruppe A stellt die Referenzprüfungen für die Löschsperren bereit
|
// Gruppe A stellt die Referenzprüfungen für die Löschsperren bereit
|
||||||
DokumentReferenzPruefung referenzPruefung = new DokumentReferenzPruefung(dokumentRepository);
|
DokumentReferenzPruefung referenzPruefung = new DokumentReferenzPruefung(dokumentRepository);
|
||||||
|
|
||||||
|
// Observer-Verteiler: Services melden Datenänderungen, Panels abonnieren
|
||||||
|
EreignisBus ereignisBus = new EreignisBus();
|
||||||
|
|
||||||
// Gruppe C — Kundenverwaltung
|
// Gruppe C — Kundenverwaltung
|
||||||
KundenVerwaltungsService kundenService = new KundenVerwaltungsService(
|
KundenVerwaltungsService kundenService = new KundenVerwaltungsService(
|
||||||
kundenRepository,
|
kundenRepository,
|
||||||
EinfacherKundennummernGenerator.ausRepository(kundenRepository),
|
EinfacherKundennummernGenerator.ausRepository(kundenRepository),
|
||||||
referenzPruefung);
|
referenzPruefung,
|
||||||
|
ereignisBus);
|
||||||
|
|
||||||
// Gruppe B — Produktverwaltung
|
// Gruppe B — Produktverwaltung
|
||||||
ProduktVerwaltungsService produktService = new ProduktVerwaltungsService(
|
ProduktVerwaltungsService produktService = new ProduktVerwaltungsService(
|
||||||
produktRepository,
|
produktRepository,
|
||||||
EinfacherProduktnummernGenerator.ausRepository(produktRepository),
|
EinfacherProduktnummernGenerator.ausRepository(produktRepository),
|
||||||
referenzPruefung);
|
referenzPruefung,
|
||||||
|
ereignisBus);
|
||||||
|
|
||||||
// Gruppe A — Dokumentenzyklus
|
// Gruppe A — Dokumentenzyklus
|
||||||
DokumentService dokumentService = new StandardDokumentService(
|
DokumentService dokumentService = new StandardDokumentService(
|
||||||
|
|
@ -67,20 +75,25 @@ public final class Main {
|
||||||
EinfacherBelegnummernGenerator.ausRepository(dokumentRepository),
|
EinfacherBelegnummernGenerator.ausRepository(dokumentRepository),
|
||||||
kundenService,
|
kundenService,
|
||||||
produktService,
|
produktService,
|
||||||
new PdfBoxPdfExporter());
|
new PdfBoxPdfExporter(),
|
||||||
|
ereignisBus);
|
||||||
|
|
||||||
// Gruppe D — Programmoberfläche
|
// Gruppe D — Programmoberfläche
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
FlatLightLaf.setup();
|
||||||
|
UIManager.put("Table.alternateRowColor", new java.awt.Color(245, 246, 248));
|
||||||
|
UIManager.put("Table.showHorizontalLines", false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Standard-Look-and-Feel verwenden
|
// Standard-Look-and-Feel verwenden
|
||||||
}
|
}
|
||||||
HauptFenster fenster = new HauptFenster(
|
HauptFenster fenster = new HauptFenster(
|
||||||
new KundenPanel(kundenService, new KundenCsvExport(kundenRepository)),
|
new KundenPanel(kundenService, new KundenCsvExport(kundenRepository),
|
||||||
new ProduktPanel(produktService, new ProduktCsvExport(produktRepository)),
|
ereignisBus),
|
||||||
|
new ProduktPanel(produktService, new ProduktCsvExport(produktRepository),
|
||||||
|
ereignisBus),
|
||||||
new DokumentListenPanel(dokumentService, kundenService, produktService,
|
new DokumentListenPanel(dokumentService, kundenService, produktService,
|
||||||
new DokumentCsvExport(dokumentRepository)));
|
new DokumentCsvExport(dokumentRepository), ereignisBus));
|
||||||
fenster.setVisible(true);
|
fenster.setVisible(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,10 @@ public class JsonDokumentRepository implements DokumentRepository {
|
||||||
|
|
||||||
private void schreibe() {
|
private void schreibe() {
|
||||||
try {
|
try {
|
||||||
if (datei.getParent() != null) {
|
|
||||||
Files.createDirectories(datei.getParent());
|
|
||||||
}
|
|
||||||
// Über den Basistyp schreiben, damit die polymorphe Typ-ID ('typ')
|
// Über den Basistyp schreiben, damit die polymorphe Typ-ID ('typ')
|
||||||
// erhalten bleibt und Belege beim Neustart wieder geladen werden (IF-01).
|
// erhalten bleibt und Belege beim Neustart wieder geladen werden (IF-01).
|
||||||
mapper.writerFor(new TypeReference<List<Dokument>>() { }).writeValue(datei.toFile(), dokumente);
|
JsonPersistenz.schreibeAtomar(datei,
|
||||||
|
mapper.writerFor(new TypeReference<List<Dokument>>() { }), dokumente);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Belegbestand konnte nicht gespeichert werden: " + datei, e);
|
throw new UncheckedIOException("Belegbestand konnte nicht gespeichert werden: " + datei, e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package de.team1.faktura.dokumente;
|
package de.team1.faktura.dokumente;
|
||||||
|
|
||||||
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.ValidierungsException;
|
||||||
import de.team1.faktura.kunden.Kunde;
|
import de.team1.faktura.kunden.Kunde;
|
||||||
import de.team1.faktura.kunden.KundenService;
|
import de.team1.faktura.kunden.KundenService;
|
||||||
|
|
@ -32,17 +34,29 @@ public class StandardDokumentService implements DokumentService {
|
||||||
private final KundenService kundenService;
|
private final KundenService kundenService;
|
||||||
private final ProduktService produktService;
|
private final ProduktService produktService;
|
||||||
private final PdfExporter pdfExporter;
|
private final PdfExporter pdfExporter;
|
||||||
|
private final EreignisBus ereignisBus;
|
||||||
|
|
||||||
public StandardDokumentService(DokumentRepository repository,
|
public StandardDokumentService(DokumentRepository repository,
|
||||||
BelegnummernGenerator nummernGenerator,
|
BelegnummernGenerator nummernGenerator,
|
||||||
KundenService kundenService,
|
KundenService kundenService,
|
||||||
ProduktService produktService,
|
ProduktService produktService,
|
||||||
PdfExporter pdfExporter) {
|
PdfExporter pdfExporter) {
|
||||||
|
this(repository, nummernGenerator, kundenService, produktService, pdfExporter,
|
||||||
|
new EreignisBus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StandardDokumentService(DokumentRepository repository,
|
||||||
|
BelegnummernGenerator nummernGenerator,
|
||||||
|
KundenService kundenService,
|
||||||
|
ProduktService produktService,
|
||||||
|
PdfExporter pdfExporter,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.nummernGenerator = nummernGenerator;
|
this.nummernGenerator = nummernGenerator;
|
||||||
this.kundenService = kundenService;
|
this.kundenService = kundenService;
|
||||||
this.produktService = produktService;
|
this.produktService = produktService;
|
||||||
this.pdfExporter = pdfExporter;
|
this.pdfExporter = pdfExporter;
|
||||||
|
this.ereignisBus = ereignisBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -58,6 +72,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
angebot.setGueltigBis(gueltigBis != null ? gueltigBis : datum.plusDays(STANDARD_GUELTIGKEIT_TAGE));
|
angebot.setGueltigBis(gueltigBis != null ? gueltigBis : datum.plusDays(STANDARD_GUELTIGKEIT_TAGE));
|
||||||
angebot.setzePositionen(dokumentpositionen);
|
angebot.setzePositionen(dokumentpositionen);
|
||||||
repository.speichere(angebot);
|
repository.speichere(angebot);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
return angebot;
|
return angebot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +88,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
ab.setzeKunde(kunde.getKundennummer(), kunde.getName(), kunde.anschrift());
|
ab.setzeKunde(kunde.getKundennummer(), kunde.getName(), kunde.anschrift());
|
||||||
ab.setzePositionen(dokumentpositionen);
|
ab.setzePositionen(dokumentpositionen);
|
||||||
repository.speichere(ab);
|
repository.speichere(ab);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
return ab;
|
return ab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +105,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
lieferschein.setLieferdatum(lieferdatum != null ? lieferdatum : datum);
|
lieferschein.setLieferdatum(lieferdatum != null ? lieferdatum : datum);
|
||||||
lieferschein.setzePositionen(dokumentpositionen);
|
lieferschein.setzePositionen(dokumentpositionen);
|
||||||
repository.speichere(lieferschein);
|
repository.speichere(lieferschein);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
return lieferschein;
|
return lieferschein;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +130,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
rechnung.setzePositionen(dokumentpositionen);
|
rechnung.setzePositionen(dokumentpositionen);
|
||||||
rechnung.setzeStatus(DokumentStatus.OFFEN);
|
rechnung.setzeStatus(DokumentStatus.OFFEN);
|
||||||
repository.speichere(rechnung);
|
repository.speichere(rechnung);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
return rechnung;
|
return rechnung;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,6 +168,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
rechnung.setzeStatus(DokumentStatus.OFFEN);
|
rechnung.setzeStatus(DokumentStatus.OFFEN);
|
||||||
}
|
}
|
||||||
repository.speichere(folgebeleg);
|
repository.speichere(folgebeleg);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
return folgebeleg;
|
return folgebeleg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +177,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
Dokument dokument = pruefeBeleg(belegnummer);
|
Dokument dokument = pruefeBeleg(belegnummer);
|
||||||
dokument.versende();
|
dokument.versende();
|
||||||
repository.speichere(dokument);
|
repository.speichere(dokument);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -169,6 +189,7 @@ public class StandardDokumentService implements DokumentService {
|
||||||
}
|
}
|
||||||
rechnung.storniere(LocalDate.now(), SYSTEM_BENUTZER);
|
rechnung.storniere(LocalDate.now(), SYSTEM_BENUTZER);
|
||||||
repository.speichere(rechnung);
|
repository.speichere(rechnung);
|
||||||
|
ereignisBus.melde(DatenBereich.DOKUMENTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package de.team1.faktura.gemeinsam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fachliche Datenbereiche der Anwendung, über die der {@link EreignisBus}
|
||||||
|
* Änderungen meldet.
|
||||||
|
*/
|
||||||
|
public enum DatenBereich {
|
||||||
|
KUNDEN,
|
||||||
|
PRODUKTE,
|
||||||
|
DOKUMENTE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package de.team1.faktura.gemeinsam;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einfacher synchroner Ereignis-Verteiler (Observer-Muster): Die Services
|
||||||
|
* melden nach jeder schreibenden Operation den geänderten {@link DatenBereich},
|
||||||
|
* die Modulansichten abonnieren die für sie relevanten Bereiche und
|
||||||
|
* aktualisieren sich selbst. Dadurch entfallen manuelle Refresh-Aufrufe
|
||||||
|
* zwischen den Modulen.
|
||||||
|
*
|
||||||
|
* <p>Alle Aufrufe laufen auf dem Event-Dispatch-Thread der Swing-Oberfläche;
|
||||||
|
* eine Synchronisierung ist daher nicht erforderlich (Einzelplatzbetrieb).
|
||||||
|
*/
|
||||||
|
public class EreignisBus {
|
||||||
|
|
||||||
|
private final Map<DatenBereich, List<Runnable>> abonnenten = new EnumMap<>(DatenBereich.class);
|
||||||
|
|
||||||
|
/** Registriert einen Beobachter für Änderungen im angegebenen Bereich. */
|
||||||
|
public void abonniere(DatenBereich bereich, Runnable beobachter) {
|
||||||
|
abonnenten.computeIfAbsent(bereich, b -> new ArrayList<>()).add(beobachter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Meldet eine Änderung im angegebenen Bereich an alle Beobachter. */
|
||||||
|
public void melde(DatenBereich bereich) {
|
||||||
|
for (Runnable beobachter : abonnenten.getOrDefault(bereich, List.of())) {
|
||||||
|
beobachter.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,16 @@ package de.team1.faktura.gemeinsam;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.AtomicMoveNotSupportedException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zentral konfigurierter Jackson-ObjectMapper für die lokale
|
* Zentral konfigurierter Jackson-ObjectMapper für die lokale
|
||||||
* JSON-Persistenz (IF-01). Datumswerte werden als ISO-Strings
|
* JSON-Persistenz (IF-01). Datumswerte werden als ISO-Strings
|
||||||
|
|
@ -23,4 +30,25 @@ public final class JsonPersistenz {
|
||||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schreibt den Bestand atomar: erst vollständig in eine temporäre Datei,
|
||||||
|
* dann per Move ersetzen. Ein Absturz während des Schreibens kann so den
|
||||||
|
* vorhandenen Bestand nicht korrumpieren (GR-01/GR-02: der Belegbestand
|
||||||
|
* ist Grundlage der lückenlosen Nummernvergabe und Unveränderlichkeit).
|
||||||
|
*/
|
||||||
|
public static void schreibeAtomar(Path datei, ObjectWriter writer, Object daten)
|
||||||
|
throws IOException {
|
||||||
|
if (datei.getParent() != null) {
|
||||||
|
Files.createDirectories(datei.getParent());
|
||||||
|
}
|
||||||
|
Path temp = datei.resolveSibling(datei.getFileName() + ".tmp");
|
||||||
|
writer.writeValue(temp.toFile(), daten);
|
||||||
|
try {
|
||||||
|
Files.move(temp, datei, StandardCopyOption.REPLACE_EXISTING,
|
||||||
|
StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
|
Files.move(temp, datei, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,17 @@ import de.team1.faktura.dokumente.Dokument;
|
||||||
import de.team1.faktura.dokumente.DokumentCsvExport;
|
import de.team1.faktura.dokumente.DokumentCsvExport;
|
||||||
import de.team1.faktura.dokumente.DokumentService;
|
import de.team1.faktura.dokumente.DokumentService;
|
||||||
import de.team1.faktura.dokumente.DokumentStatus;
|
import de.team1.faktura.dokumente.DokumentStatus;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.kunden.KundenService;
|
import de.team1.faktura.kunden.KundenService;
|
||||||
import de.team1.faktura.produkte.ProduktService;
|
import de.team1.faktura.produkte.ProduktService;
|
||||||
|
|
||||||
|
import javax.swing.DefaultListCellRenderer;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JList;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
|
|
@ -20,6 +23,7 @@ import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.awt.FlowLayout;
|
import java.awt.FlowLayout;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
|
|
@ -48,8 +52,9 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
private final DokumentCsvExport datenExport;
|
private final DokumentCsvExport datenExport;
|
||||||
private final DokumentListenController controller;
|
private final DokumentListenController controller;
|
||||||
|
|
||||||
private final JComboBox<String> statusFilter = new JComboBox<>(
|
/** Statusfilter; {@code null} steht für "Alle" (F-06). */
|
||||||
new String[]{"Alle", "ENTWURF", "OFFEN", "VERSENDET", "STORNIERT"});
|
private final JComboBox<DokumentStatus> statusFilter = new JComboBox<>(
|
||||||
|
statusFilterWerte());
|
||||||
private final DokumentTabellenModel tabellenModel = new DokumentTabellenModel();
|
private final DokumentTabellenModel tabellenModel = new DokumentTabellenModel();
|
||||||
private final JTable tabelle = new JTable(tabellenModel);
|
private final JTable tabelle = new JTable(tabellenModel);
|
||||||
|
|
||||||
|
|
@ -63,7 +68,8 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
public DokumentListenPanel(DokumentService dokumentService,
|
public DokumentListenPanel(DokumentService dokumentService,
|
||||||
KundenService kundenService,
|
KundenService kundenService,
|
||||||
ProduktService produktService,
|
ProduktService produktService,
|
||||||
DokumentCsvExport datenExport) {
|
DokumentCsvExport datenExport,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.dokumentService = dokumentService;
|
this.dokumentService = dokumentService;
|
||||||
this.kundenService = kundenService;
|
this.kundenService = kundenService;
|
||||||
this.produktService = produktService;
|
this.produktService = produktService;
|
||||||
|
|
@ -71,6 +77,9 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
this.controller = new DokumentListenController(dokumentService);
|
this.controller = new DokumentListenController(dokumentService);
|
||||||
baueOberflaeche();
|
baueOberflaeche();
|
||||||
aktualisiere();
|
aktualisiere();
|
||||||
|
ereignisBus.abonniere(DatenBereich.DOKUMENTE, this::aktualisiere);
|
||||||
|
// Kundenname und -nummer werden in der Belegliste angezeigt
|
||||||
|
ereignisBus.abonniere(DatenBereich.KUNDEN, this::aktualisiere);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void baueOberflaeche() {
|
private void baueOberflaeche() {
|
||||||
|
|
@ -79,6 +88,14 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
JPanel kopf = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
JPanel kopf = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
kopf.add(new JLabel("Statusfilter:"));
|
kopf.add(new JLabel("Statusfilter:"));
|
||||||
kopf.add(statusFilter);
|
kopf.add(statusFilter);
|
||||||
|
statusFilter.setRenderer(new DefaultListCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList<?> liste, Object wert,
|
||||||
|
int index, boolean selektiert, boolean fokus) {
|
||||||
|
return super.getListCellRendererComponent(liste,
|
||||||
|
wert == null ? "Alle" : wert, index, selektiert, fokus);
|
||||||
|
}
|
||||||
|
});
|
||||||
statusFilter.addActionListener(e -> aktualisiere());
|
statusFilter.addActionListener(e -> aktualisiere());
|
||||||
|
|
||||||
JButton neueRechnung = new JButton("Neue Rechnung (Assistent)…");
|
JButton neueRechnung = new JButton("Neue Rechnung (Assistent)…");
|
||||||
|
|
@ -94,6 +111,9 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
|
|
||||||
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
tabelle.getSelectionModel().addListSelectionListener(e -> aktualisiereAktionen());
|
tabelle.getSelectionModel().addListSelectionListener(e -> aktualisiereAktionen());
|
||||||
|
TabellenFormat.konfiguriere(tabelle);
|
||||||
|
tabelle.getColumnModel().getColumn(4).setCellRenderer(TabellenFormat.waehrungsRenderer());
|
||||||
|
tabelle.getColumnModel().getColumn(5).setCellRenderer(TabellenFormat.statusRenderer());
|
||||||
add(new JScrollPane(tabelle), BorderLayout.CENTER);
|
add(new JScrollPane(tabelle), BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel aktionen = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
JPanel aktionen = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
|
@ -115,7 +135,8 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
|
|
||||||
private Dokument auswahl() {
|
private Dokument auswahl() {
|
||||||
int zeile = tabelle.getSelectedRow();
|
int zeile = tabelle.getSelectedRow();
|
||||||
return zeile < 0 ? null : tabellenModel.dokumente.get(zeile);
|
return zeile < 0 ? null
|
||||||
|
: tabellenModel.dokumente.get(tabelle.convertRowIndexToModel(zeile));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Aktiviert/deaktiviert die Belegaktionen gemäß Status (F-08, F-14). */
|
/** Aktiviert/deaktiviert die Belegaktionen gemäß Status (F-08, F-14). */
|
||||||
|
|
@ -144,14 +165,12 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
RechnungsWizardDialog dialog = new RechnungsWizardDialog(fenster, wizardController,
|
RechnungsWizardDialog dialog = new RechnungsWizardDialog(fenster, wizardController,
|
||||||
kundenService, produktService);
|
kundenService, produktService);
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
aktualisiere();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void oeffneBelegDialog() {
|
private void oeffneBelegDialog() {
|
||||||
Window fenster = SwingUtilities.getWindowAncestor(this);
|
Window fenster = SwingUtilities.getWindowAncestor(this);
|
||||||
BelegDialog dialog = new BelegDialog(fenster, dokumentService, kundenService, produktService);
|
BelegDialog dialog = new BelegDialog(fenster, dokumentService, kundenService, produktService);
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
aktualisiere();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void erzeugeFolgebeleg() {
|
private void erzeugeFolgebeleg() {
|
||||||
|
|
@ -159,15 +178,12 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
if (dokument == null) {
|
if (dokument == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, null, () -> {
|
||||||
Dokument folgebeleg = dokumentService.erzeugeFolgebeleg(dokument.getBelegnummer());
|
Dokument folgebeleg = dokumentService.erzeugeFolgebeleg(dokument.getBelegnummer());
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg(folgebeleg.belegtyp().anzeigename() + " "
|
MeldungsAnzeige.zeige(this, Meldung.erfolg(folgebeleg.belegtyp().anzeigename() + " "
|
||||||
+ folgebeleg.getBelegnummer() + " wurde aus " + dokument.getBelegnummer()
|
+ folgebeleg.getBelegnummer() + " wurde aus " + dokument.getBelegnummer()
|
||||||
+ " erzeugt."), null);
|
+ " erzeugt."), null);
|
||||||
} catch (ValidierungsException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(e.getFeldname(), e.getMessage()), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void versende() {
|
private void versende() {
|
||||||
|
|
@ -182,14 +198,11 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
if (antwort != JOptionPane.YES_OPTION) {
|
if (antwort != JOptionPane.YES_OPTION) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, null, () -> {
|
||||||
dokumentService.versende(dokument.getBelegnummer());
|
dokumentService.versende(dokument.getBelegnummer());
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Beleg " + dokument.getBelegnummer()
|
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Beleg " + dokument.getBelegnummer()
|
||||||
+ " ist jetzt im Status VERSENDET."), null);
|
+ " ist jetzt im Status VERSENDET."), null);
|
||||||
} catch (IllegalStateException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(null, e.getMessage()), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stornierung mit Bestätigungsdialog: Rechnungsnummer und Bruttosumme (F-15). */
|
/** Stornierung mit Bestätigungsdialog: Rechnungsnummer und Bruttosumme (F-15). */
|
||||||
|
|
@ -204,7 +217,6 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
"Rechnung stornieren", JOptionPane.YES_NO_OPTION);
|
"Rechnung stornieren", JOptionPane.YES_NO_OPTION);
|
||||||
Meldung meldung = controller.storniere(dokument.getBelegnummer(),
|
Meldung meldung = controller.storniere(dokument.getBelegnummer(),
|
||||||
antwort == JOptionPane.YES_OPTION);
|
antwort == JOptionPane.YES_OPTION);
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, meldung, null);
|
MeldungsAnzeige.zeige(this, meldung, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,17 +289,18 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void aktualisiere() {
|
public void aktualisiere() {
|
||||||
DokumentStatus filter = switch ((String) statusFilter.getSelectedItem()) {
|
tabellenModel.setze(controller.gefiltert((DokumentStatus) statusFilter.getSelectedItem()));
|
||||||
case "ENTWURF" -> DokumentStatus.ENTWURF;
|
|
||||||
case "OFFEN" -> DokumentStatus.OFFEN;
|
|
||||||
case "VERSENDET" -> DokumentStatus.VERSENDET;
|
|
||||||
case "STORNIERT" -> DokumentStatus.STORNIERT;
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
tabellenModel.setze(controller.gefiltert(filter));
|
|
||||||
aktualisiereAktionen();
|
aktualisiereAktionen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** "Alle" (= {@code null}) gefolgt von allen Belegstatus. */
|
||||||
|
private static DokumentStatus[] statusFilterWerte() {
|
||||||
|
DokumentStatus[] status = DokumentStatus.values();
|
||||||
|
DokumentStatus[] werte = new DokumentStatus[status.length + 1];
|
||||||
|
System.arraycopy(status, 0, werte, 1, status.length);
|
||||||
|
return werte;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class DokumentTabellenModel extends AbstractTableModel {
|
private static final class DokumentTabellenModel extends AbstractTableModel {
|
||||||
|
|
||||||
private static final String[] SPALTEN =
|
private static final String[] SPALTEN =
|
||||||
|
|
@ -315,6 +328,15 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
return SPALTEN[spalte];
|
return SPALTEN[spalte];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int spalte) {
|
||||||
|
return switch (spalte) {
|
||||||
|
case 4 -> java.math.BigDecimal.class;
|
||||||
|
case 5 -> DokumentStatus.class;
|
||||||
|
default -> String.class;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int zeile, int spalte) {
|
public Object getValueAt(int zeile, int spalte) {
|
||||||
Dokument dokument = dokumente.get(zeile);
|
Dokument dokument = dokumente.get(zeile);
|
||||||
|
|
@ -323,8 +345,8 @@ public class DokumentListenPanel extends JPanel implements ModulPanel {
|
||||||
case 1 -> dokument.belegtyp().anzeigename();
|
case 1 -> dokument.belegtyp().anzeigename();
|
||||||
case 2 -> dokument.getDatum() == null ? "" : DATUM.format(dokument.getDatum());
|
case 2 -> dokument.getDatum() == null ? "" : DATUM.format(dokument.getDatum());
|
||||||
case 3 -> dokument.getKundeName() + " (" + dokument.getKundenReferenz() + ")";
|
case 3 -> dokument.getKundeName() + " (" + dokument.getKundenReferenz() + ")";
|
||||||
case 4 -> dokument.getSummeBrutto().toPlainString() + " EUR";
|
case 4 -> dokument.getSummeBrutto();
|
||||||
default -> dokument.getStatus().name();
|
default -> dokument.getStatus();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package de.team1.faktura.gui;
|
package de.team1.faktura.gui;
|
||||||
|
|
||||||
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.kunden.Kunde;
|
import de.team1.faktura.kunden.Kunde;
|
||||||
import de.team1.faktura.kunden.KundenCsvExport;
|
import de.team1.faktura.kunden.KundenCsvExport;
|
||||||
import de.team1.faktura.kunden.KundenVerwaltungsService;
|
import de.team1.faktura.kunden.KundenVerwaltungsService;
|
||||||
|
|
@ -56,7 +56,8 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
private String gewaehlteNummer;
|
private String gewaehlteNummer;
|
||||||
private boolean ungespeichert;
|
private boolean ungespeichert;
|
||||||
|
|
||||||
public KundenPanel(KundenVerwaltungsService service, KundenCsvExport csvExport) {
|
public KundenPanel(KundenVerwaltungsService service, KundenCsvExport csvExport,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.csvExport = csvExport;
|
this.csvExport = csvExport;
|
||||||
felder.put("Name", nameFeld);
|
felder.put("Name", nameFeld);
|
||||||
|
|
@ -66,6 +67,7 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
felder.put("E-Mail", eMailFeld);
|
felder.put("E-Mail", eMailFeld);
|
||||||
baueOberflaeche();
|
baueOberflaeche();
|
||||||
aktualisiere();
|
aktualisiere();
|
||||||
|
ereignisBus.abonniere(DatenBereich.KUNDEN, this::aktualisiere);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void baueOberflaeche() {
|
private void baueOberflaeche() {
|
||||||
|
|
@ -78,6 +80,7 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
add(suchleiste, BorderLayout.NORTH);
|
add(suchleiste, BorderLayout.NORTH);
|
||||||
|
|
||||||
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
TabellenFormat.konfiguriere(tabelle);
|
||||||
tabelle.getSelectionModel().addListSelectionListener(e -> {
|
tabelle.getSelectionModel().addListSelectionListener(e -> {
|
||||||
if (!e.getValueIsAdjusting()) {
|
if (!e.getValueIsAdjusting()) {
|
||||||
ladeAuswahl();
|
ladeAuswahl();
|
||||||
|
|
@ -148,7 +151,7 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void speichere() {
|
private void speichere() {
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, felder, () -> {
|
||||||
Kunde kunde = gewaehlteNummer == null
|
Kunde kunde = gewaehlteNummer == null
|
||||||
? new Kunde()
|
? new Kunde()
|
||||||
: service.findeKunde(gewaehlteNummer);
|
: service.findeKunde(gewaehlteNummer);
|
||||||
|
|
@ -166,12 +169,9 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
ungespeichert = false;
|
ungespeichert = false;
|
||||||
gewaehlteNummer = gespeichert.getKundennummer();
|
gewaehlteNummer = gespeichert.getKundennummer();
|
||||||
nummerAnzeige.setText(gespeichert.getKundennummer());
|
nummerAnzeige.setText(gespeichert.getKundennummer());
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Kunde wurde gespeichert. Kundennummer: "
|
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Kunde wurde gespeichert. Kundennummer: "
|
||||||
+ gespeichert.getKundennummer()), felder);
|
+ gespeichert.getKundennummer()), felder);
|
||||||
} catch (ValidierungsException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(e.getFeldname(), e.getMessage()), felder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loesche() {
|
private void loesche() {
|
||||||
|
|
@ -184,14 +184,11 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
if (antwort != JOptionPane.YES_OPTION) {
|
if (antwort != JOptionPane.YES_OPTION) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, felder, () -> {
|
||||||
service.loescheKunde(gewaehlteNummer);
|
service.loescheKunde(gewaehlteNummer);
|
||||||
leereFormular();
|
leereFormular();
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Kunde wurde gelöscht."), felder);
|
MeldungsAnzeige.zeige(this, Meldung.erfolg("Der Kunde wurde gelöscht."), felder);
|
||||||
} catch (LoeschAbgelehntException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(null, e.getMessage()), felder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportiere() {
|
private void exportiere() {
|
||||||
|
|
@ -209,7 +206,7 @@ public class KundenPanel extends JPanel implements ModulPanel {
|
||||||
if (zeile < 0) {
|
if (zeile < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Kunde kunde = tabellenModel.kunden.get(zeile);
|
Kunde kunde = tabellenModel.kunden.get(tabelle.convertRowIndexToModel(zeile));
|
||||||
gewaehlteNummer = kunde.getKundennummer();
|
gewaehlteNummer = kunde.getKundennummer();
|
||||||
nummerAnzeige.setText(kunde.getKundennummer());
|
nummerAnzeige.setText(kunde.getKundennummer());
|
||||||
nameFeld.setText(kunde.getName());
|
nameFeld.setText(kunde.getName());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package de.team1.faktura.gui;
|
package de.team1.faktura.gui;
|
||||||
|
|
||||||
|
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
||||||
|
import de.team1.faktura.gemeinsam.ValidierungsException;
|
||||||
|
|
||||||
import javax.swing.BorderFactory;
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
|
@ -7,6 +10,7 @@ import javax.swing.UIManager;
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,4 +52,24 @@ public final class MeldungsAnzeige {
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zentrale Fehlerbehandlung der Modulansichten: führt die Aktion aus und
|
||||||
|
* zeigt fachliche Fehler einheitlich an — Validierungsfehler mit
|
||||||
|
* Feldmarkierung (Q-09), abgelehnte Löschvorgänge (GR-04), unzulässige
|
||||||
|
* Statuswechsel (GR-02) und Persistenzfehler beim Speichern (IF-01).
|
||||||
|
*/
|
||||||
|
public static void mitFehlerbehandlung(Component parent, Map<String, JComponent> felder,
|
||||||
|
Runnable aktion) {
|
||||||
|
try {
|
||||||
|
aktion.run();
|
||||||
|
} catch (ValidierungsException e) {
|
||||||
|
zeige(parent, Meldung.fehler(e.getFeldname(), e.getMessage()), felder);
|
||||||
|
} catch (LoeschAbgelehntException | IllegalStateException e) {
|
||||||
|
zeige(parent, Meldung.fehler(null, e.getMessage()), felder);
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
|
zeige(parent, Meldung.fehler(null,
|
||||||
|
"Die Daten konnten nicht gespeichert werden: " + e.getMessage()), felder);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package de.team1.faktura.gui;
|
package de.team1.faktura.gui;
|
||||||
|
|
||||||
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.ValidierungsException;
|
||||||
import de.team1.faktura.produkte.Produkt;
|
import de.team1.faktura.produkte.Produkt;
|
||||||
import de.team1.faktura.produkte.ProduktCsvExport;
|
import de.team1.faktura.produkte.ProduktCsvExport;
|
||||||
|
|
@ -58,7 +59,8 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
private String gewaehlteNummer;
|
private String gewaehlteNummer;
|
||||||
private boolean ungespeichert;
|
private boolean ungespeichert;
|
||||||
|
|
||||||
public ProduktPanel(ProduktVerwaltungsService service, ProduktCsvExport csvExport) {
|
public ProduktPanel(ProduktVerwaltungsService service, ProduktCsvExport csvExport,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.csvExport = csvExport;
|
this.csvExport = csvExport;
|
||||||
felder.put("Bezeichnung", bezeichnungFeld);
|
felder.put("Bezeichnung", bezeichnungFeld);
|
||||||
|
|
@ -66,6 +68,7 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
felder.put("Steuersatz", steuersatzWahl);
|
felder.put("Steuersatz", steuersatzWahl);
|
||||||
baueOberflaeche();
|
baueOberflaeche();
|
||||||
aktualisiere();
|
aktualisiere();
|
||||||
|
ereignisBus.abonniere(DatenBereich.PRODUKTE, this::aktualisiere);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void baueOberflaeche() {
|
private void baueOberflaeche() {
|
||||||
|
|
@ -78,6 +81,8 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
add(suchleiste, BorderLayout.NORTH);
|
add(suchleiste, BorderLayout.NORTH);
|
||||||
|
|
||||||
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
tabelle.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
TabellenFormat.konfiguriere(tabelle);
|
||||||
|
tabelle.getColumnModel().getColumn(2).setCellRenderer(TabellenFormat.waehrungsRenderer());
|
||||||
tabelle.getSelectionModel().addListSelectionListener(e -> {
|
tabelle.getSelectionModel().addListSelectionListener(e -> {
|
||||||
if (!e.getValueIsAdjusting()) {
|
if (!e.getValueIsAdjusting()) {
|
||||||
ladeAuswahl();
|
ladeAuswahl();
|
||||||
|
|
@ -145,7 +150,7 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void speichere() {
|
private void speichere() {
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, felder, () -> {
|
||||||
Produkt produkt = gewaehlteNummer == null
|
Produkt produkt = gewaehlteNummer == null
|
||||||
? new Produkt()
|
? new Produkt()
|
||||||
: service.findeProdukt(gewaehlteNummer);
|
: service.findeProdukt(gewaehlteNummer);
|
||||||
|
|
@ -161,12 +166,9 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
ungespeichert = false;
|
ungespeichert = false;
|
||||||
gewaehlteNummer = gespeichert.getProduktnummer();
|
gewaehlteNummer = gespeichert.getProduktnummer();
|
||||||
nummerAnzeige.setText(gespeichert.getProduktnummer());
|
nummerAnzeige.setText(gespeichert.getProduktnummer());
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg("Das Produkt wurde gespeichert. Produktnummer: "
|
MeldungsAnzeige.zeige(this, Meldung.erfolg("Das Produkt wurde gespeichert. Produktnummer: "
|
||||||
+ gespeichert.getProduktnummer()), felder);
|
+ gespeichert.getProduktnummer()), felder);
|
||||||
} catch (ValidierungsException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(e.getFeldname(), e.getMessage()), felder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Akzeptiert deutsches und englisches Dezimaltrennzeichen. */
|
/** Akzeptiert deutsches und englisches Dezimaltrennzeichen. */
|
||||||
|
|
@ -202,14 +204,11 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
if (antwort != JOptionPane.YES_OPTION) {
|
if (antwort != JOptionPane.YES_OPTION) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
MeldungsAnzeige.mitFehlerbehandlung(this, felder, () -> {
|
||||||
service.loescheProdukt(gewaehlteNummer);
|
service.loescheProdukt(gewaehlteNummer);
|
||||||
leereFormular();
|
leereFormular();
|
||||||
aktualisiere();
|
|
||||||
MeldungsAnzeige.zeige(this, Meldung.erfolg("Das Produkt wurde gelöscht."), felder);
|
MeldungsAnzeige.zeige(this, Meldung.erfolg("Das Produkt wurde gelöscht."), felder);
|
||||||
} catch (LoeschAbgelehntException e) {
|
});
|
||||||
MeldungsAnzeige.zeige(this, Meldung.fehler(null, e.getMessage()), felder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportiere() {
|
private void exportiere() {
|
||||||
|
|
@ -227,7 +226,7 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
if (zeile < 0) {
|
if (zeile < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Produkt produkt = tabellenModel.produkte.get(zeile);
|
Produkt produkt = tabellenModel.produkte.get(tabelle.convertRowIndexToModel(zeile));
|
||||||
gewaehlteNummer = produkt.getProduktnummer();
|
gewaehlteNummer = produkt.getProduktnummer();
|
||||||
nummerAnzeige.setText(produkt.getProduktnummer());
|
nummerAnzeige.setText(produkt.getProduktnummer());
|
||||||
bezeichnungFeld.setText(produkt.getBezeichnung());
|
bezeichnungFeld.setText(produkt.getBezeichnung());
|
||||||
|
|
@ -335,13 +334,18 @@ public class ProduktPanel extends JPanel implements ModulPanel {
|
||||||
return SPALTEN[spalte];
|
return SPALTEN[spalte];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getColumnClass(int spalte) {
|
||||||
|
return spalte == 2 ? BigDecimal.class : String.class;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int zeile, int spalte) {
|
public Object getValueAt(int zeile, int spalte) {
|
||||||
Produkt produkt = produkte.get(zeile);
|
Produkt produkt = produkte.get(zeile);
|
||||||
return switch (spalte) {
|
return switch (spalte) {
|
||||||
case 0 -> produkt.getProduktnummer();
|
case 0 -> produkt.getProduktnummer();
|
||||||
case 1 -> produkt.getBezeichnung();
|
case 1 -> produkt.getBezeichnung();
|
||||||
case 2 -> produkt.getEinzelpreisNetto().toPlainString() + " EUR";
|
case 2 -> produkt.getEinzelpreisNetto();
|
||||||
case 3 -> produkt.getSteuersatz().multiply(new BigDecimal("100"))
|
case 3 -> produkt.getSteuersatz().multiply(new BigDecimal("100"))
|
||||||
.stripTrailingZeros().toPlainString() + " %";
|
.stripTrailingZeros().toPlainString() + " %";
|
||||||
default -> produkt.getEinheit() == null ? "" : produkt.getEinheit();
|
default -> produkt.getEinheit() == null ? "" : produkt.getEinheit();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package de.team1.faktura.gui;
|
||||||
|
|
||||||
|
import de.team1.faktura.dokumente.DokumentStatus;
|
||||||
|
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gemeinsame Tabellendarstellung der Modulansichten (Q-05): Währungsbeträge
|
||||||
|
* rechtsbündig im deutschen Format, Belegstatus farblich hervorgehoben,
|
||||||
|
* einheitliche Zeilenhöhe und Sortierung per Spaltenkopf.
|
||||||
|
*/
|
||||||
|
public final class TabellenFormat {
|
||||||
|
|
||||||
|
private TabellenFormat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Formatiert einen Betrag im deutschen Währungsformat, z. B. "1.234,50 €". */
|
||||||
|
public static String formatiereBetrag(BigDecimal betrag) {
|
||||||
|
return NumberFormat.getCurrencyInstance(Locale.GERMANY).format(betrag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Einheitliche Grundeinstellungen: Zeilenhöhe und Sortierung per Spaltenkopf. */
|
||||||
|
public static void konfiguriere(JTable tabelle) {
|
||||||
|
tabelle.setRowHeight(24);
|
||||||
|
tabelle.setAutoCreateRowSorter(true);
|
||||||
|
tabelle.setFillsViewportHeight(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rechtsbündiger Renderer für {@link BigDecimal}-Beträge im Währungsformat. */
|
||||||
|
public static DefaultTableCellRenderer waehrungsRenderer() {
|
||||||
|
DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() {
|
||||||
|
@Override
|
||||||
|
protected void setValue(Object wert) {
|
||||||
|
setText(wert instanceof BigDecimal betrag ? formatiereBetrag(betrag) : "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
renderer.setHorizontalAlignment(JLabel.RIGHT);
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renderer für {@link DokumentStatus}: dezente Statusfarbe, Selektion bleibt lesbar. */
|
||||||
|
public static DefaultTableCellRenderer statusRenderer() {
|
||||||
|
return new DefaultTableCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable tabelle, Object wert,
|
||||||
|
boolean selektiert, boolean fokus, int zeile, int spalte) {
|
||||||
|
super.getTableCellRendererComponent(tabelle, wert, selektiert, fokus, zeile, spalte);
|
||||||
|
if (wert instanceof DokumentStatus status) {
|
||||||
|
setText(status.name());
|
||||||
|
setForeground(selektiert ? tabelle.getSelectionForeground() : farbeFuer(status));
|
||||||
|
} else {
|
||||||
|
setForeground(selektiert ? tabelle.getSelectionForeground()
|
||||||
|
: tabelle.getForeground());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color farbeFuer(DokumentStatus status) {
|
||||||
|
return switch (status) {
|
||||||
|
case ENTWURF -> new Color(110, 110, 110);
|
||||||
|
case OFFEN -> new Color(0, 90, 180);
|
||||||
|
case VERSENDET -> new Color(0, 130, 60);
|
||||||
|
case STORNIERT -> new Color(180, 40, 40);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,10 +42,7 @@ public class JsonKundenRepository implements KundenRepository {
|
||||||
|
|
||||||
private void schreibe() {
|
private void schreibe() {
|
||||||
try {
|
try {
|
||||||
if (datei.getParent() != null) {
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), kunden);
|
||||||
Files.createDirectories(datei.getParent());
|
|
||||||
}
|
|
||||||
mapper.writeValue(datei.toFile(), kunden);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Kundenbestand konnte nicht gespeichert werden: " + datei, e);
|
throw new UncheckedIOException("Kundenbestand konnte nicht gespeichert werden: " + datei, e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package de.team1.faktura.kunden;
|
package de.team1.faktura.kunden;
|
||||||
|
|
||||||
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.ValidierungsException;
|
||||||
|
|
||||||
|
|
@ -15,20 +17,31 @@ public class KundenVerwaltungsService implements KundenService {
|
||||||
private final KundenRepository repository;
|
private final KundenRepository repository;
|
||||||
private final KundennummernGenerator nummernGenerator;
|
private final KundennummernGenerator nummernGenerator;
|
||||||
private final KundenReferenzPruefung referenzPruefung;
|
private final KundenReferenzPruefung referenzPruefung;
|
||||||
|
private final EreignisBus ereignisBus;
|
||||||
|
|
||||||
public KundenVerwaltungsService(KundenRepository repository,
|
public KundenVerwaltungsService(KundenRepository repository,
|
||||||
KundennummernGenerator nummernGenerator,
|
KundennummernGenerator nummernGenerator,
|
||||||
KundenReferenzPruefung referenzPruefung) {
|
KundenReferenzPruefung referenzPruefung) {
|
||||||
|
this(repository, nummernGenerator, referenzPruefung, new EreignisBus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public KundenVerwaltungsService(KundenRepository repository,
|
||||||
|
KundennummernGenerator nummernGenerator,
|
||||||
|
KundenReferenzPruefung referenzPruefung,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.nummernGenerator = nummernGenerator;
|
this.nummernGenerator = nummernGenerator;
|
||||||
this.referenzPruefung = referenzPruefung;
|
this.referenzPruefung = referenzPruefung;
|
||||||
|
this.ereignisBus = ereignisBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legt einen neuen Kunden an und vergibt die Kundennummer (F-01, F-02). */
|
/** Legt einen neuen Kunden an und vergibt die Kundennummer (F-01, F-02). */
|
||||||
public Kunde legeAn(Kunde kunde) {
|
public Kunde legeAn(Kunde kunde) {
|
||||||
validiere(kunde);
|
validiere(kunde);
|
||||||
kunde.setKundennummer(nummernGenerator.naechsteNummer());
|
kunde.setKundennummer(nummernGenerator.naechsteNummer());
|
||||||
return repository.speichere(kunde);
|
Kunde gespeichert = repository.speichere(kunde);
|
||||||
|
ereignisBus.melde(DatenBereich.KUNDEN);
|
||||||
|
return gespeichert;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ändert einen bestehenden Kunden; die Pflichtfeldprüfung gilt unverändert (F-05). */
|
/** Ändert einen bestehenden Kunden; die Pflichtfeldprüfung gilt unverändert (F-05). */
|
||||||
|
|
@ -37,7 +50,9 @@ public class KundenVerwaltungsService implements KundenService {
|
||||||
throw new ValidierungsException("Kundennummer", "Der Kunde wurde noch nicht angelegt.");
|
throw new ValidierungsException("Kundennummer", "Der Kunde wurde noch nicht angelegt.");
|
||||||
}
|
}
|
||||||
validiere(kunde);
|
validiere(kunde);
|
||||||
return repository.speichere(kunde);
|
Kunde gespeichert = repository.speichere(kunde);
|
||||||
|
ereignisBus.melde(DatenBereich.KUNDEN);
|
||||||
|
return gespeichert;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,6 +67,7 @@ public class KundenVerwaltungsService implements KundenService {
|
||||||
+ anzahl + " verknüpfte Dokumente vorhanden (GR-04).");
|
+ anzahl + " verknüpfte Dokumente vorhanden (GR-04).");
|
||||||
}
|
}
|
||||||
repository.loesche(kundennummer);
|
repository.loesche(kundennummer);
|
||||||
|
ereignisBus.melde(DatenBereich.KUNDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Kunde> alleSortiertNachName() {
|
public List<Kunde> alleSortiertNachName() {
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,7 @@ public class JsonProduktRepository implements ProduktRepository {
|
||||||
|
|
||||||
private void schreibe() {
|
private void schreibe() {
|
||||||
try {
|
try {
|
||||||
if (datei.getParent() != null) {
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), produkte);
|
||||||
Files.createDirectories(datei.getParent());
|
|
||||||
}
|
|
||||||
mapper.writeValue(datei.toFile(), produkte);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Produktbestand konnte nicht gespeichert werden: " + datei, e);
|
throw new UncheckedIOException("Produktbestand konnte nicht gespeichert werden: " + datei, e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package de.team1.faktura.produkte;
|
package de.team1.faktura.produkte;
|
||||||
|
|
||||||
|
import de.team1.faktura.gemeinsam.DatenBereich;
|
||||||
|
import de.team1.faktura.gemeinsam.EreignisBus;
|
||||||
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
import de.team1.faktura.gemeinsam.LoeschAbgelehntException;
|
||||||
import de.team1.faktura.gemeinsam.ValidierungsException;
|
import de.team1.faktura.gemeinsam.ValidierungsException;
|
||||||
|
|
||||||
|
|
@ -20,20 +22,31 @@ public class ProduktVerwaltungsService implements ProduktService {
|
||||||
private final ProduktRepository repository;
|
private final ProduktRepository repository;
|
||||||
private final ProduktnummernGenerator nummernGenerator;
|
private final ProduktnummernGenerator nummernGenerator;
|
||||||
private final ProduktReferenzPruefung referenzPruefung;
|
private final ProduktReferenzPruefung referenzPruefung;
|
||||||
|
private final EreignisBus ereignisBus;
|
||||||
|
|
||||||
public ProduktVerwaltungsService(ProduktRepository repository,
|
public ProduktVerwaltungsService(ProduktRepository repository,
|
||||||
ProduktnummernGenerator nummernGenerator,
|
ProduktnummernGenerator nummernGenerator,
|
||||||
ProduktReferenzPruefung referenzPruefung) {
|
ProduktReferenzPruefung referenzPruefung) {
|
||||||
|
this(repository, nummernGenerator, referenzPruefung, new EreignisBus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProduktVerwaltungsService(ProduktRepository repository,
|
||||||
|
ProduktnummernGenerator nummernGenerator,
|
||||||
|
ProduktReferenzPruefung referenzPruefung,
|
||||||
|
EreignisBus ereignisBus) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.nummernGenerator = nummernGenerator;
|
this.nummernGenerator = nummernGenerator;
|
||||||
this.referenzPruefung = referenzPruefung;
|
this.referenzPruefung = referenzPruefung;
|
||||||
|
this.ereignisBus = ereignisBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Legt ein neues Produkt an und vergibt die Produktnummer (F-01, F-02). */
|
/** Legt ein neues Produkt an und vergibt die Produktnummer (F-01, F-02). */
|
||||||
public Produkt legeAn(Produkt produkt) {
|
public Produkt legeAn(Produkt produkt) {
|
||||||
validiere(produkt);
|
validiere(produkt);
|
||||||
produkt.setProduktnummer(nummernGenerator.naechsteNummer());
|
produkt.setProduktnummer(nummernGenerator.naechsteNummer());
|
||||||
return repository.speichere(produkt);
|
Produkt gespeichert = repository.speichere(produkt);
|
||||||
|
ereignisBus.melde(DatenBereich.PRODUKTE);
|
||||||
|
return gespeichert;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,7 +58,9 @@ public class ProduktVerwaltungsService implements ProduktService {
|
||||||
throw new ValidierungsException("Produktnummer", "Das Produkt wurde noch nicht angelegt.");
|
throw new ValidierungsException("Produktnummer", "Das Produkt wurde noch nicht angelegt.");
|
||||||
}
|
}
|
||||||
validiere(produkt);
|
validiere(produkt);
|
||||||
return repository.speichere(produkt);
|
Produkt gespeichert = repository.speichere(produkt);
|
||||||
|
ereignisBus.melde(DatenBereich.PRODUKTE);
|
||||||
|
return gespeichert;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -59,6 +74,7 @@ public class ProduktVerwaltungsService implements ProduktService {
|
||||||
+ "es wird in Dokumenten verwendet.");
|
+ "es wird in Dokumenten verwendet.");
|
||||||
}
|
}
|
||||||
repository.loesche(produktnummer);
|
repository.loesche(produktnummer);
|
||||||
|
ereignisBus.melde(DatenBereich.PRODUKTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Produkt> alleSortiertNachBezeichnung() {
|
public List<Produkt> alleSortiertNachBezeichnung() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package de.team1.faktura.gemeinsam;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class EreignisBusTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("melde benachrichtigt alle Beobachter des Bereichs")
|
||||||
|
void benachrichtigtAlleBeobachter() {
|
||||||
|
EreignisBus bus = new EreignisBus();
|
||||||
|
AtomicInteger ersterBeobachter = new AtomicInteger();
|
||||||
|
AtomicInteger zweiterBeobachter = new AtomicInteger();
|
||||||
|
bus.abonniere(DatenBereich.KUNDEN, ersterBeobachter::incrementAndGet);
|
||||||
|
bus.abonniere(DatenBereich.KUNDEN, zweiterBeobachter::incrementAndGet);
|
||||||
|
|
||||||
|
bus.melde(DatenBereich.KUNDEN);
|
||||||
|
bus.melde(DatenBereich.KUNDEN);
|
||||||
|
|
||||||
|
assertEquals(2, ersterBeobachter.get());
|
||||||
|
assertEquals(2, zweiterBeobachter.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("melde benachrichtigt nur Beobachter des betroffenen Bereichs")
|
||||||
|
void benachrichtigtNurBetroffenenBereich() {
|
||||||
|
EreignisBus bus = new EreignisBus();
|
||||||
|
AtomicInteger kundenBeobachter = new AtomicInteger();
|
||||||
|
AtomicInteger dokumentBeobachter = new AtomicInteger();
|
||||||
|
bus.abonniere(DatenBereich.KUNDEN, kundenBeobachter::incrementAndGet);
|
||||||
|
bus.abonniere(DatenBereich.DOKUMENTE, dokumentBeobachter::incrementAndGet);
|
||||||
|
|
||||||
|
bus.melde(DatenBereich.DOKUMENTE);
|
||||||
|
|
||||||
|
assertEquals(0, kundenBeobachter.get());
|
||||||
|
assertEquals(1, dokumentBeobachter.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("melde ohne Beobachter ist wirkungslos und wirft nicht")
|
||||||
|
void meldenOhneBeobachterIstWirkungslos() {
|
||||||
|
new EreignisBus().melde(DatenBereich.PRODUKTE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package de.team1.faktura.gemeinsam;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class JsonPersistenzTest {
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = JsonPersistenz.mapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("schreibeAtomar legt die Zieldatei an und lässt keine Temp-Datei zurück")
|
||||||
|
void schreibtOhneTempDateiRueckstand(@TempDir Path verzeichnis) throws IOException {
|
||||||
|
Path datei = verzeichnis.resolve("bestand.json");
|
||||||
|
|
||||||
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), List.of("a", "b"));
|
||||||
|
|
||||||
|
assertTrue(Files.exists(datei));
|
||||||
|
assertFalse(Files.exists(verzeichnis.resolve("bestand.json.tmp")),
|
||||||
|
"Temp-Datei darf nach dem Move nicht zurückbleiben");
|
||||||
|
List<?> gelesen = mapper.readValue(datei.toFile(), List.class);
|
||||||
|
assertEquals(List.of("a", "b"), gelesen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("schreibeAtomar ersetzt einen vorhandenen Bestand vollständig")
|
||||||
|
void ersetztVorhandenenBestand(@TempDir Path verzeichnis) throws IOException {
|
||||||
|
Path datei = verzeichnis.resolve("bestand.json");
|
||||||
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), List.of("alt"));
|
||||||
|
|
||||||
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), List.of("neu1", "neu2"));
|
||||||
|
|
||||||
|
List<?> gelesen = mapper.readValue(datei.toFile(), List.class);
|
||||||
|
assertEquals(List.of("neu1", "neu2"), gelesen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("schreibeAtomar legt fehlende Elternverzeichnisse an")
|
||||||
|
void legtElternverzeichnisseAn(@TempDir Path verzeichnis) throws IOException {
|
||||||
|
Path datei = verzeichnis.resolve("unterordner").resolve("bestand.json");
|
||||||
|
|
||||||
|
JsonPersistenz.schreibeAtomar(datei, mapper.writer(), List.of("a"));
|
||||||
|
|
||||||
|
assertTrue(Files.exists(datei));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue