fix: GUI-Kompatibilität DokumentenService Fassade + DokumentPanel gefixt

main
Andreas Ivanovic 2026-06-28 18:46:13 +02:00
parent 9f22667592
commit 92dbd53986
7 changed files with 430 additions and 381 deletions

View File

@ -99,3 +99,33 @@ public abstract class Dokument {
getClass().getSimpleName(), dokumentNummer, kundenId, datum, status); getClass().getSimpleName(), dokumentNummer, kundenId, datum, status);
} }
} }
// === Alias-Methoden für GUI-Kompatibilität ===
/** Alias für getDokumentNummer() GUI-Kompatibilität. */
public String getDokumentnummer() { return getDokumentNummer(); }
/** Alias für getKundenId() GUI-Kompatibilität. */
public long getKundeId() { return getKundenId(); }
/** Alias für getNettoSumme() GUI-Kompatibilität. */
public BigDecimal getNettosumme() { return getNettoSumme(); }
/** Alias für getBruttoSumme() GUI-Kompatibilität. */
public BigDecimal getBruttosumme() { return getBruttoSumme(); }
/**
* Gibt an ob dieses Dokument festgeschrieben ist.
* Standard: false. Wird in Rechnung auf true gesetzt (PHGUI11).
*/
public boolean isFestgeschrieben() { return false; }
/** Gibt den Typ als lesbaren String zurück (für Tabelle in GUI). */
public String getTypBezeichnung() {
return switch (getTyp()) {
case ANGEBOT -> "Angebot";
case AUFTRAGSBESTAETIGUNG -> "Auftragsbestätigung";
case LIEFERSCHEIN -> "Lieferschein";
case RECHNUNG -> "Rechnung";
};
}

View File

@ -72,3 +72,22 @@ public class DokumentPosition {
produktId, bezeichnungSnapshot, menge, einzelpreisNettoSnapshot, mwstSatzSnapshot); produktId, bezeichnungSnapshot, menge, einzelpreisNettoSnapshot, mwstSatzSnapshot);
} }
} }
// === Alias-Methoden für GUI-Kompatibilität ===
/** Alias für getBezeichnungSnapshot() GUI-Kompatibilität. */
public String getProduktbezeichnung() { return getBezeichnungSnapshot(); }
/** Alias für getEinzelpreisNettoSnapshot() GUI-Kompatibilität. */
public BigDecimal getEinzelpreisNetto() { return getEinzelpreisNettoSnapshot(); }
/**
* MwSt-Satz in Prozent (z. B. 19.00 statt 0.19) GUI-Kompatibilität.
*/
public BigDecimal getMehrwertsteuerSatz() {
return getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100))
.setScale(0, java.math.RoundingMode.HALF_UP);
}
/** Alias für getNettoGesamt() GUI-Kompatibilität. */
public BigDecimal getSumme() { return getNettoGesamt(); }

View File

@ -2,12 +2,7 @@ package de.hsmannheim.faktura.dokumente.domain;
/** /**
* Steuert erlaubte Folgeaktionen im Dokumentenprozess. * Steuert erlaubte Folgeaktionen im Dokumentenprozess.
* * Workflow: ANGEBOT: OFFEN UEBERFUEHRT | AB: BESTAETIGT | LS: GELIEFERT | R: FESTGESCHRIEBEN
* Workflow:
* ANGEBOT: OFFEN UEBERFUEHRT
* AUFTRAGSBESTAETIGUNG: BESTAETIGT
* LIEFERSCHEIN: GELIEFERT
* RECHNUNG: FESTGESCHRIEBEN
*/ */
public enum DokumentStatus { public enum DokumentStatus {
OFFEN, OFFEN,
@ -15,5 +10,17 @@ public enum DokumentStatus {
BESTAETIGT, BESTAETIGT,
GELIEFERT, GELIEFERT,
FAKTURIERT, FAKTURIERT,
FESTGESCHRIEBEN FESTGESCHRIEBEN;
/** Lesbarer Anzeigename für die GUI (PHGUI10). */
public String getAnzeigename() {
return switch (this) {
case OFFEN -> "Offen";
case UEBERFUEHRT -> "Überführt";
case BESTAETIGT -> "Bestätigt";
case GELIEFERT -> "Geliefert";
case FAKTURIERT -> "Fakturiert";
case FESTGESCHRIEBEN -> "Festgeschrieben";
};
}
} }

View File

@ -75,3 +75,19 @@ public interface DokumentRepository {
*/ */
int findMaxLaufendeNummer(String praefix, int jahr); int findMaxLaufendeNummer(String praefix, int jahr);
} }
/**
* Gibt alle gespeicherten Dokumente zurück (für die Listenansicht in der GUI).
*
* @return Liste aller Dokumente
*/
List<Dokument> findAll();
/**
* Löscht ein Dokument anhand seiner Nummer.
* Festgeschriebene Rechnungen können nicht gelöscht werden.
*
* @param nummer Dokumentnummer
* @return true wenn gelöscht, false wenn nicht gefunden
*/
boolean delete(String nummer);

View File

@ -95,3 +95,13 @@ public class InMemoryDokumentRepository implements DokumentRepository {
store.clear(); store.clear();
} }
} }
@Override
public List<Dokument> findAll() {
return new ArrayList<>(store.values());
}
@Override
public boolean delete(String nummer) {
return store.remove(nummer) != null;
}

View File

@ -0,0 +1,143 @@
package de.hsmannheim.faktura.dokumente.service;
import de.hsmannheim.faktura.dokumente.domain.*;
import de.hsmannheim.faktura.dokumente.export.SimplePdfExportService;
import de.hsmannheim.faktura.dokumente.lookup.*;
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
/**
* Fassade für die GUI (DokumentPanel).
* Kapselt den DokumentServiceImpl und bietet die Methoden die die GUI braucht.
*
* Wird in der GUI so instanziiert:
* new DokumentenService(new InMemoryDokumentRepository())
*
* Sobald Gruppe G (Produkte) und H (Kunden) ihre Lookups liefern,
* können diese über den Setter-Konstruktor injiziert werden.
*/
public class DokumentenService {
private final DokumentService service;
private final DokumentRepository repository;
/**
* Konstruktor für die GUI nimmt ein Repository entgegen.
* Produkt- und Kundenlookup werden als Stubs vorbelegt.
*/
public DokumentenService(DokumentRepository repository) {
this.repository = repository;
// Stub-Lookups bis Gruppe G und H ihre Implementierungen liefern
ProductLookup produktLookup = new StubProductLookup();
CustomerLookup kundeLookup = new StubCustomerLookup();
this.service = new DokumentServiceImpl(
repository,
produktLookup,
kundeLookup,
new SimplePdfExportService()
);
}
/**
* Vollständiger Konstruktor für Produktion wenn echte Lookups vorliegen.
*/
public DokumentenService(DokumentRepository repository,
ProductLookup produktLookup,
CustomerLookup kundeLookup) {
this.repository = repository;
this.service = new DokumentServiceImpl(
repository, produktLookup, kundeLookup, new SimplePdfExportService()
);
}
// -------------------------------------------------------------------------
// Methoden für die GUI
// -------------------------------------------------------------------------
/** Alle Dokumente für die Listenansicht. */
public List<Dokument> getAllDokumente() {
return repository.findAll();
}
/** Einzelnes Dokument nach Nummer laden. */
public Optional<Dokument> getDokumentByNummer(String nummer) {
return repository.findByNummer(nummer);
}
/**
* Dokument löschen. Festgeschriebene Rechnungen können nicht gelöscht werden.
* @return true wenn erfolgreich gelöscht
*/
public boolean deleteDokument(String nummer) {
Optional<Dokument> dok = repository.findByNummer(nummer);
if (dok.isEmpty()) return false;
if (dok.get().isFestgeschrieben()) return false; // PHGUI11
return repository.delete(nummer);
}
/** Auftragsbestätigung aus Angebot erzeugen. */
public Auftragsbestaetigung createAuftragsbestaetigung(String angebotsNummer) {
return service.erstelleAuftragsbestaetigung(angebotsNummer);
}
/** Lieferschein aus AB erzeugen (Lieferdatum = heute). */
public Lieferschein createLieferschein(String auftragsNummer) {
return service.erstelleLieferschein(auftragsNummer, LocalDate.now());
}
/** Rechnung aus Lieferschein erzeugen. */
public Rechnung createRechnung(String lieferscheinNummer) {
return service.erstelleRechnung(lieferscheinNummer);
}
/** PDF-Export eines Dokuments. */
public Path exportPdf(String dokumentNummer, DokumentType typ) {
return service.exportiereAlsPdf(dokumentNummer);
}
// -------------------------------------------------------------------------
// Stub-Implementierungen (Platzhalter bis Gruppe G und H fertig sind)
// -------------------------------------------------------------------------
/**
* Platzhalter-Produktlookup.
* Ersetzen durch echte Implementierung von Gruppe G.
*/
private static class StubProductLookup implements ProductLookup {
@Override
public Optional<ProductDto> findProduct(long produktId) {
// Beispieldaten bis Gruppe G ihre Implementierung liefert
return Optional.of(new ProductDto(
produktId,
"Produkt #" + produktId,
new BigDecimal("100.00"),
new BigDecimal("0.19")
));
}
}
/**
* Platzhalter-Kundenlookup.
* Ersetzen durch echte Implementierung von Gruppe H.
*/
private static class StubCustomerLookup implements CustomerLookup {
@Override
public Optional<CustomerDto> findCustomer(long kundenId) {
// Beispieldaten bis Gruppe H ihre Implementierung liefert
return Optional.of(new CustomerDto(
kundenId,
"Kunde #" + kundenId,
"Musterstraße 1, 68163 Mannheim",
"DE123456789"
));
}
}
}

View File

@ -3,69 +3,33 @@ package de.hsmannheim.faktura.gui;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.*; import javax.swing.table.*;
import java.awt.*; import java.awt.*;
import java.io.File;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional;
import de.hsmannheim.faktura.dokumente.domain.Dokument; import de.hsmannheim.faktura.dokumente.domain.Dokument;
import de.hsmannheim.faktura.dokumente.domain.DokumentPosition;
import de.hsmannheim.faktura.dokumente.domain.DokumentStatus; import de.hsmannheim.faktura.dokumente.domain.DokumentStatus;
import de.hsmannheim.faktura.dokumente.domain.DokumentType; import de.hsmannheim.faktura.dokumente.service.DokumentenService;
import de.hsmannheim.faktura.dokumente.export.PdfExportService;
import de.hsmannheim.faktura.dokumente.export.SimplePdfExportService;
import de.hsmannheim.faktura.dokumente.lookup.CustomerDto;
import de.hsmannheim.faktura.dokumente.lookup.CustomerLookup;
import de.hsmannheim.faktura.dokumente.lookup.ProductDto;
import de.hsmannheim.faktura.dokumente.lookup.ProductLookup;
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository; import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository;
import de.hsmannheim.faktura.dokumente.service.DokumentService;
import de.hsmannheim.faktura.dokumente.service.DokumentServiceImpl;
import de.hsmannheim.faktura.kunde.repository.KundeRepositoryJson;
import de.hsmannheim.faktura.kunde.service.BelegReferenzPruefer;
import de.hsmannheim.faktura.kunde.service.KundeService;
import de.hsmannheim.faktura.kunde.service.KundeValidator;
import de.hsmannheim.faktura.kunde.model.Kunde;
/** /**
* Panel für den Dokumentenprozess (PHGUI10, PHGUI11, PHGUI12). * Panel für den Dokumentenprozess (PHGUI10, PHGUI11, PHGUI12).
* * Zeigt Status und zulässige Folgeaktionen an. Deaktiviert unzulässige Aktionen (PHGUI10).
* Verdrahtung gegen die spec-konforme Schnittstelle aus dem * Festgeschriebene Rechnungen sind schreibgeschützt (PHGUI11).
* Pflichtenheft Gruppe F v1.0 (Kap. 6.2):
* - Service: {@link DokumentService} / {@link DokumentServiceImpl}
* - Repository: {@link DokumentRepository} / {@link InMemoryDokumentRepository}
* - Methoden: erstelleAuftragsbestaetigung / erstelleLieferschein /
* erstelleRechnung / exportiereAlsPdf
* - Status: OFFEN, UEBERFUEHRT, BESTAETIGT, GELIEFERT, FAKTURIERT,
* FESTGESCHRIEBEN
*
* Die ursprüngliche GUI-Verdrahtung (DokumentenService, getDokumentnummer,
* RECHNUNG_ERSTELLT, deleteDokument, ...) findet sich in keinem Pflichtenheft
* wieder und wurde hier auf die gemeinsame Spec gemappt. OP-6 (GUI vs.
* PH v1.1 / Gruppe F PH v1.0).
*/ */
public class DokumentPanel extends JPanel { public class DokumentPanel extends JPanel {
private final MainFrame mainFrame; private final MainFrame mainFrame;
private final DokumentService dokumentService; private final DokumentenService dokumentenService;
private final TrackingDokumentRepository tracking;
// Tabelle // Tabelle
private DefaultTableModel tableModel; private DefaultTableModel tableModel;
private JTable dokumentTable; private JTable dokumentTable;
// Detailfelder (schreibgeschützt) // Detailfelder (schreibgeschützt)
private JTextField txtDokumentNummer; private JTextField txtDokumentnummer;
private JTextField txtKundenId; private JTextField txtKundenId;
private JTextField txtDatum; private JTextField txtDatum;
private JTextField txtStatus; private JTextField txtStatus;
private JCheckBox chkFestgeschrieben; private JCheckBox chkFestgeschrieben;
// Positionstabelle // Positionstabelle
private DefaultTableModel posTableModel; private DefaultTableModel posTableModel;
@ -78,6 +42,8 @@ public class DokumentPanel extends JPanel {
// Aktions-Buttons (PHGUI10) // Aktions-Buttons (PHGUI10)
private JButton btnNeu; private JButton btnNeu;
private JButton btnSpeichern;
private JButton btnLoeschen;
private JButton btnAbbrechen; private JButton btnAbbrechen;
private JButton btnPdfExport; private JButton btnPdfExport;
@ -90,53 +56,18 @@ public class DokumentPanel extends JPanel {
public DokumentPanel(MainFrame mainFrame) { public DokumentPanel(MainFrame mainFrame) {
this.mainFrame = mainFrame; this.mainFrame = mainFrame;
// FIX: InMemoryDokumentRepository statt DokumentRepository (Interface)
// DokumentServiceImpl braucht 4 Abhängigkeiten (DI laut Gruppe F): this.dokumentenService = new DokumentenService(new InMemoryDokumentRepository());
// Repository, ProductLookup, CustomerLookup, PdfExportService.
this.tracking = new TrackingDokumentRepository(new InMemoryDokumentRepository());
PdfExportService pdfExport = new SimplePdfExportService(Path.of("exports"));
CustomerLookup customerLookup = new KundeCustomerLookup(buildKundeService());
ProductLookup productLookup = stubProductLookup();
this.dokumentService = new DokumentServiceImpl(
tracking, productLookup, customerLookup, pdfExport);
setLayout(new BorderLayout(8, 8)); setLayout(new BorderLayout(8, 8));
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
initComponents(); initComponents();
ladeDokumente(); ladeDokumente();
} }
/**
* KundeService nur als Datenquelle für den CustomerLookup-Adapter
* (K3 aus PH v1.1). Eigener Aufbau, da das Panel self-contained ist
* geteiltes Wiring zwischen Panels ist Bestandteil von OP-6.
*/
private KundeService buildKundeService() {
return new KundeService(
new KundeRepositoryJson(new File("data/kunden.json")),
new KundeValidator(),
(BelegReferenzPruefer) (kundeId -> false));
}
/**
* DokumentPanel kann derzeit keine neuen Angebote anlegen
* (Eingabemaske fehlt im UI), deshalb wird der ProductLookup nicht
* aktiv genutzt. Stub ist ausreichend, damit DokumentServiceImpl
* verdrahtet werden kann.
*/
private ProductLookup stubProductLookup() {
return produktId -> Optional.<ProductDto>empty();
}
// -------------------------------------------------------------------------
// UI-Aufbau
// -------------------------------------------------------------------------
private void initComponents() { private void initComponents() {
// --- Linke Seite: Dokumentenliste --- // --- Linke Seite: Dokumentenliste ---
JPanel leftPanel = new JPanel(new BorderLayout(4, 4)); JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
leftPanel.setPreferredSize(new Dimension(320, 0)); leftPanel.setPreferredSize(new Dimension(300, 0));
String[] cols = {"Nr.", "Typ", "Status"}; String[] cols = {"Nr.", "Typ", "Status"};
tableModel = new DefaultTableModel(cols, 0) { tableModel = new DefaultTableModel(cols, 0) {
@ -146,9 +77,14 @@ public class DokumentPanel extends JPanel {
// Status-Spalte farbig (PHGUI10) // Status-Spalte farbig (PHGUI10)
public Component prepareRenderer(TableCellRenderer r, int row, int col) { public Component prepareRenderer(TableCellRenderer r, int row, int col) {
Component c = super.prepareRenderer(r, row, col); Component c = super.prepareRenderer(r, row, col);
if (col == 2 && !isRowSelected(row)) { if (col == 2) {
String status = String.valueOf(getValueAt(row, col)); String status = (String) getValueAt(row, col);
c.setForeground(farbeFuerStatus(status)); if (status != null) {
if (status.contains("Offen")) c.setForeground(new Color(41, 128, 185));
else if (status.contains("Bestätigt")) c.setForeground(new Color(39, 174, 96));
else if (status.contains("Festgeschr")) c.setForeground(new Color(192, 57, 43));
else c.setForeground(Color.BLACK);
}
} else { } else {
c.setForeground(Color.BLACK); c.setForeground(Color.BLACK);
} }
@ -157,266 +93,264 @@ public class DokumentPanel extends JPanel {
}; };
dokumentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); dokumentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
dokumentTable.setAutoCreateRowSorter(true); dokumentTable.setAutoCreateRowSorter(true);
dokumentTable.getTableHeader().setReorderingAllowed(false);
dokumentTable.getSelectionModel().addListSelectionListener(e -> { dokumentTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) zeigeAusgewaehltes(); if (!e.getValueIsAdjusting()) zeigeAusgewaehltes();
}); });
leftPanel.add(new JLabel("Dokumente"), BorderLayout.NORTH);
leftPanel.add(new JScrollPane(dokumentTable), BorderLayout.CENTER); leftPanel.add(new JScrollPane(dokumentTable), BorderLayout.CENTER);
// --- Rechte Seite: Detailbereich --- // --- Rechte Seite: Detailansicht ---
JPanel rightPanel = new JPanel(new BorderLayout(0, 8)); JPanel rightPanel = new JPanel(new BorderLayout(4, 8));
rightPanel.add(kopfPanel(), BorderLayout.NORTH);
rightPanel.add(positionsBereich(), BorderLayout.CENTER);
rightPanel.add(buttonPanel(), BorderLayout.SOUTH);
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel); JPanel headerPanel = new JPanel(new GridBagLayout());
split.setDividerLocation(320); headerPanel.setBorder(BorderFactory.createTitledBorder("Dokumentdetail"));
add(split, BorderLayout.CENTER);
}
private JPanel kopfPanel() {
JPanel p = new JPanel(new GridBagLayout());
p.setBorder(BorderFactory.createTitledBorder("Dokumentkopf"));
GridBagConstraints g = new GridBagConstraints(); GridBagConstraints g = new GridBagConstraints();
g.insets = new Insets(4, 6, 2, 6); g.insets = new Insets(3, 4, 3, 4);
g.anchor = GridBagConstraints.WEST; g.fill = GridBagConstraints.HORIZONTAL;
g.fill = GridBagConstraints.HORIZONTAL; g.weightx = 0.5;
g.weightx = 1.0;
txtDokumentNummer = createReadOnlyField(); // Festgeschrieben-Checkbox (PHGUI11)
txtKundenId = createReadOnlyField(); chkFestgeschrieben = new JCheckBox("Festgeschrieben");
txtDatum = createReadOnlyField();
txtStatus = createReadOnlyField();
chkFestgeschrieben = new JCheckBox("Festgeschrieben (PHGUI11)");
chkFestgeschrieben.setEnabled(false); chkFestgeschrieben.setEnabled(false);
g.gridx = 1; g.gridy = 0; g.anchor = GridBagConstraints.EAST;
headerPanel.add(chkFestgeschrieben, g);
int row = 0; g.anchor = GridBagConstraints.WEST;
row = addLabelField(p, g, "Dokumentnummer:", txtDokumentNummer, row); g.gridx = 0; g.gridy = 1; headerPanel.add(new JLabel("Dokumentnummer"), g);
row = addLabelField(p, g, "Kunden-ID:", txtKundenId, row); g.gridx = 1; headerPanel.add(new JLabel("Kundennummer"), g);
row = addLabelField(p, g, "Datum:", txtDatum, row); txtDokumentnummer = createReadOnlyField();
row = addLabelField(p, g, "Status:", txtStatus, row); txtKundenId = createReadOnlyField();
g.gridx = 0; g.gridy = 2; headerPanel.add(txtDokumentnummer, g);
g.gridx = 1; headerPanel.add(txtKundenId, g);
g.gridx = 0; g.gridy = row; g.gridwidth = 2; p.add(chkFestgeschrieben, g); g.gridx = 0; g.gridy = 3; headerPanel.add(new JLabel("Datum"), g);
g.gridwidth = 1; g.gridx = 1; headerPanel.add(new JLabel("Status"), g);
return p; txtDatum = createReadOnlyField();
} txtStatus = createReadOnlyField();
g.gridx = 0; g.gridy = 4; headerPanel.add(txtDatum, g);
g.gridx = 1; headerPanel.add(txtStatus, g);
private JPanel positionsBereich() { rightPanel.add(headerPanel, BorderLayout.NORTH);
JPanel p = new JPanel(new BorderLayout(0, 4));
p.setBorder(BorderFactory.createTitledBorder("Positionen / Summen"));
String[] posCols = {"Pos.", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Gesamt"}; // Positionstabelle
String[] posCols = {"Pos", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Summe"};
posTableModel = new DefaultTableModel(posCols, 0) { posTableModel = new DefaultTableModel(posCols, 0) {
public boolean isCellEditable(int r, int c) { return false; } public boolean isCellEditable(int r, int c) { return false; }
}; };
posTable = new JTable(posTableModel); posTable = new JTable(posTableModel);
posTable.getTableHeader().setReorderingAllowed(false); posTable.setEnabled(false);
p.add(new JScrollPane(posTable), BorderLayout.CENTER);
JPanel summenPanel = new JPanel(new GridLayout(1, 6, 8, 0)); JPanel posPanel = new JPanel(new BorderLayout());
summenPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); posPanel.setBorder(BorderFactory.createTitledBorder("Positionen:"));
summenPanel.add(new JLabel("Netto:")); posPanel.add(new JScrollPane(posTable), BorderLayout.CENTER);
lblNetto = new JLabel("0.00 €");
summenPanel.add(lblNetto);
summenPanel.add(new JLabel("USt.:"));
lblUst = new JLabel("0.00 €");
summenPanel.add(lblUst);
summenPanel.add(new JLabel("Brutto:"));
lblBrutto = new JLabel("0.00 €");
summenPanel.add(lblBrutto);
p.add(summenPanel, BorderLayout.SOUTH);
return p;
}
private JPanel buttonPanel() { // Summen
JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4)); JPanel summenPanel = new JPanel(new GridLayout(3, 2, 4, 2));
summenPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
lblNetto = new JLabel("0,00 €"); lblNetto.setHorizontalAlignment(SwingConstants.RIGHT);
lblUst = new JLabel("0,00 €"); lblUst.setHorizontalAlignment(SwingConstants.RIGHT);
lblBrutto = new JLabel("0,00 €", SwingConstants.RIGHT);
lblBrutto.setFont(new Font("SansSerif", Font.BOLD, 13));
summenPanel.add(new JLabel("Netto:")); summenPanel.add(lblNetto);
summenPanel.add(new JLabel("USt.:")); summenPanel.add(lblUst);
summenPanel.add(new JLabel("Brutto:")); summenPanel.add(lblBrutto);
btnNeu = new JButton("Auswahl zurücksetzen"); JPanel mittelPanel = new JPanel(new BorderLayout());
btnAbbrechen = new JButton("Abbrechen"); mittelPanel.add(posPanel, BorderLayout.CENTER);
btnPdfExport = new JButton("PDF-Export"); mittelPanel.add(summenPanel, BorderLayout.SOUTH);
btnAngebotZuAuftrag = new JButton("→ Auftragsbestätigung"); rightPanel.add(mittelPanel, BorderLayout.CENTER);
btnAuftragZuLieferschein = new JButton("→ Lieferschein");
btnLieferscheinZuRechnung = new JButton("→ Rechnung");
// --- Aktionen (PHGUI10: nur zulässige aktiv) ---
JPanel aktionenPanel = new JPanel(new BorderLayout());
aktionenPanel.setBorder(BorderFactory.createTitledBorder("Aktionen:"));
JPanel stdButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
btnNeu = new JButton("Neu");
btnSpeichern = new JButton("Speichern");
btnLoeschen = new JButton("Löschen");
btnAbbrechen = new JButton("Abbrechen");
btnPdfExport = new JButton("PDF-Export");
btnPdfExport.setBackground(new Color(41, 128, 185));
btnPdfExport.setForeground(Color.WHITE);
stdButtons.add(btnNeu);
stdButtons.add(btnSpeichern);
stdButtons.add(btnLoeschen);
stdButtons.add(btnAbbrechen);
stdButtons.add(btnPdfExport);
JPanel folgeButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
btnAngebotZuAuftrag = new JButton("→ Auftragsbestätigung erstellen");
btnAuftragZuLieferschein = new JButton("→ Lieferschein erstellen");
btnLieferscheinZuRechnung = new JButton("→ Rechnung erstellen");
folgeButtons.add(btnAngebotZuAuftrag);
folgeButtons.add(btnAuftragZuLieferschein);
folgeButtons.add(btnLieferscheinZuRechnung);
aktionenPanel.add(stdButtons, BorderLayout.NORTH);
aktionenPanel.add(folgeButtons, BorderLayout.SOUTH);
rightPanel.add(aktionenPanel, BorderLayout.SOUTH);
// Button-Actions
btnNeu.addActionListener(e -> neuesFormular()); btnNeu.addActionListener(e -> neuesFormular());
btnAbbrechen.addActionListener(e -> neuesFormular()); btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); });
btnPdfExport.addActionListener(e -> pdfExport()); btnPdfExport.addActionListener(e -> pdfExport());
btnAngebotZuAuftrag.addActionListener(e -> btnLoeschen.addActionListener(e -> loeschen());
folgeaktion(DokumentType.ANGEBOT));
btnAuftragZuLieferschein.addActionListener(e ->
folgeaktion(DokumentType.AUFTRAGSBESTAETIGUNG));
btnLieferscheinZuRechnung.addActionListener(e ->
folgeaktion(DokumentType.LIEFERSCHEIN));
p.add(btnAngebotZuAuftrag); btnAngebotZuAuftrag.addActionListener(e -> folgeaktion("ANGEBOT_ZU_AUFTRAG"));
p.add(btnAuftragZuLieferschein); btnAuftragZuLieferschein.addActionListener(e -> folgeaktion("AUFTRAG_ZU_LIEFERSCHEIN"));
p.add(btnLieferscheinZuRechnung); btnLieferscheinZuRechnung.addActionListener(e -> folgeaktion("LIEFERSCHEIN_ZU_RECHNUNG"));
p.add(btnPdfExport);
p.add(btnNeu);
p.add(btnAbbrechen);
// Initial alles deaktiviert (es ist nichts ausgewählt)
setAktionenDeaktiviert(); setAktionenDeaktiviert();
return p;
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
split.setDividerLocation(300);
add(split, BorderLayout.CENTER);
} }
private int addLabelField(JPanel p, GridBagConstraints g, // --- Daten ---
String label, JComponent field, int row) {
g.weighty = 0;
g.gridx = 0; g.gridy = row; g.weightx = 0; p.add(new JLabel(label), g);
g.gridx = 1; g.gridy = row; g.weightx = 1.0; p.add(field, g);
return row + 1;
}
// -------------------------------------------------------------------------
// Daten laden / anzeigen
// -------------------------------------------------------------------------
private void ladeDokumente() { private void ladeDokumente() {
tableModel.setRowCount(0); tableModel.setRowCount(0);
for (Dokument d : tracking.findAll()) { List<Dokument> alle = dokumentenService.getAllDokumente();
for (Dokument d : alle) {
tableModel.addRow(new Object[]{ tableModel.addRow(new Object[]{
d.getDokumentNummer(), d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer()
typBezeichnung(d.getTyp()), d.getTypBezeichnung(),
d.getStatus().name() d.getStatus().getAnzeigename()
}); });
} }
} }
private void zeigeAusgewaehltes() { private void zeigeAusgewaehltes() {
int row = dokumentTable.getSelectedRow(); int row = dokumentTable.getSelectedRow();
if (row < 0) { aktuellesDokument = null; return; } if (row < 0) return;
int modelRow = dokumentTable.convertRowIndexToModel(row); int modelRow = dokumentTable.convertRowIndexToModel(row);
String nr = (String) tableModel.getValueAt(modelRow, 0); String nr = (String) tableModel.getValueAt(modelRow, 0);
tracking.findByNummer(nr).ifPresent(this::zeigeDokument); dokumentenService.getDokumentByNummer(nr).ifPresent(d -> {
} aktuellesDokument = d;
txtDokumentnummer.setText(d.getDokumentNummer()); // FIX
txtKundenId.setText(String.valueOf(d.getKundenId())); // FIX
txtDatum.setText(d.getDatum().toString());
txtStatus.setText(d.getStatus().getAnzeigename());
private void zeigeDokument(Dokument d) { chkFestgeschrieben.setSelected(d.isFestgeschrieben());
aktuellesDokument = d;
txtDokumentNummer.setText(d.getDokumentNummer());
txtKundenId.setText(Long.toString(d.getKundenId()));
txtDatum.setText(d.getDatum() == null ? "" : d.getDatum().toString());
txtStatus.setText(d.getStatus().name());
boolean festgeschrieben = d.getStatus() == DokumentStatus.FESTGESCHRIEBEN; // Positionen laden
chkFestgeschrieben.setSelected(festgeschrieben); posTableModel.setRowCount(0);
int pos = 1;
// Positionen for (var position : d.getPositionen()) {
posTableModel.setRowCount(0); posTableModel.addRow(new Object[]{
int pos = 1;
boolean lieferschein = d.getTyp() == DokumentType.LIEFERSCHEIN; // PH-DP-07
for (DokumentPosition p : d.getPositionen()) {
posTableModel.addRow(new Object[]{
pos++, pos++,
p.getBezeichnungSnapshot(), position.getBezeichnungSnapshot(), // FIX
p.getMenge(), position.getMenge(),
lieferschein ? "—" : (p.getEinzelpreisNettoSnapshot() + " €"), position.getEinzelpreisNettoSnapshot() + " €", // FIX
lieferschein ? "—" : (formatProzent(p.getMwstSatzSnapshot())), position.getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100))
lieferschein ? "—" : (p.getNettoGesamt() + " €") .setScale(0, java.math.RoundingMode.HALF_UP) + "%",// FIX
}); position.getNettoGesamt() + " €" // FIX
} });
}
// Summen (Lieferschein zeigt keine Preise PH-DP-07) // Summen (nicht für Lieferschein)
if (lieferschein) { if (d.getNettoSumme() != null) { // FIX
lblNetto.setText("—"); lblNetto.setText(d.getNettoSumme().toPlainString() + " €"); // FIX
lblUst.setText("—"); lblUst.setText(d.getUstSumme().toPlainString() + " €");
lblBrutto.setText("—"); lblBrutto.setText(d.getBruttoSumme().toPlainString() + " €"); // FIX
} else { }
lblNetto.setText(nullSichBd(d.getNettoSumme()) + " €");
lblUst.setText( nullSichBd(d.getUstSumme()) + " €");
lblBrutto.setText(nullSichBd(d.getBruttoSumme())+ " €");
}
aktualisiereAktionen(d); aktualisiereAktionen(d);
});
} }
private String nullSichBd(BigDecimal v) { return v == null ? "0.00" : v.toPlainString(); }
/** /**
* Aktiviert nur die zulässigen Folgeaktionen basierend auf * Aktiviert nur zulässige Folgeaktionen basierend auf Dokumentstatus (PHGUI10).
* Dokumenttyp und -status (PHGUI10). * Festgeschriebene Rechnungen: nur PDF-Export aktiv (PHGUI11).
* Festgeschriebene Rechnungen: alle Aktionen außer PDF-Export
* deaktiviert (PHGUI11).
*/ */
private void aktualisiereAktionen(Dokument d) { private void aktualisiereAktionen(Dokument d) {
DokumentType typ = d.getTyp();
DokumentStatus status = d.getStatus(); DokumentStatus status = d.getStatus();
boolean festgeschrieben = d.isFestgeschrieben();
if (status == DokumentStatus.FESTGESCHRIEBEN) { // PHGUI11: Festgeschriebene Rechnung → nur PDF-Export
if (festgeschrieben) {
btnNeu.setEnabled(true);
btnSpeichern.setEnabled(false);
btnLoeschen.setEnabled(false);
btnAbbrechen.setEnabled(true);
btnPdfExport.setEnabled(true); // PHGUI12
btnAngebotZuAuftrag.setEnabled(false); btnAngebotZuAuftrag.setEnabled(false);
btnAuftragZuLieferschein.setEnabled(false); btnAuftragZuLieferschein.setEnabled(false);
btnLieferscheinZuRechnung.setEnabled(false); btnLieferscheinZuRechnung.setEnabled(false);
btnPdfExport.setEnabled(true); // PHGUI12
return; return;
} }
btnPdfExport.setEnabled(true); // PHGUI10: Folgeaktionen je nach Status (FIX: korrekte Status-Konstanten)
btnAngebotZuAuftrag.setEnabled( btnNeu.setEnabled(true);
typ == DokumentType.ANGEBOT && status == DokumentStatus.OFFEN); btnSpeichern.setEnabled(true);
btnAuftragZuLieferschein.setEnabled( btnLoeschen.setEnabled(true);
typ == DokumentType.AUFTRAGSBESTAETIGUNG && status == DokumentStatus.BESTAETIGT); btnAbbrechen.setEnabled(true);
btnLieferscheinZuRechnung.setEnabled( btnPdfExport.setEnabled(status == DokumentStatus.FESTGESCHRIEBEN); // FIX
typ == DokumentType.LIEFERSCHEIN && status == DokumentStatus.GELIEFERT);
btnAngebotZuAuftrag.setEnabled(status == DokumentStatus.OFFEN); // FIX
btnAuftragZuLieferschein.setEnabled(status == DokumentStatus.BESTAETIGT); // FIX
btnLieferscheinZuRechnung.setEnabled(status == DokumentStatus.GELIEFERT); // FIX
} }
private void setAktionenDeaktiviert() { private void setAktionenDeaktiviert() {
btnSpeichern.setEnabled(false);
btnLoeschen.setEnabled(false);
btnPdfExport.setEnabled(false); btnPdfExport.setEnabled(false);
btnAngebotZuAuftrag.setEnabled(false); btnAngebotZuAuftrag.setEnabled(false);
btnAuftragZuLieferschein.setEnabled(false); btnAuftragZuLieferschein.setEnabled(false);
btnLieferscheinZuRechnung.setEnabled(false); btnLieferscheinZuRechnung.setEnabled(false);
} }
// -------------------------------------------------------------------------
// Aktionen
// -------------------------------------------------------------------------
private void neuesFormular() { private void neuesFormular() {
aktuellesDokument = null; aktuellesDokument = null;
dokumentTable.clearSelection(); txtDokumentnummer.setText(""); txtKundenId.setText("");
txtDokumentNummer.setText(""); txtDatum.setText(""); txtStatus.setText("");
txtKundenId.setText("");
txtDatum.setText("");
txtStatus.setText("");
chkFestgeschrieben.setSelected(false); chkFestgeschrieben.setSelected(false);
posTableModel.setRowCount(0); posTableModel.setRowCount(0);
lblNetto.setText("0.00 €"); lblNetto.setText("0,00 €"); lblUst.setText("0,00 €"); lblBrutto.setText("0,00 €");
lblUst.setText("0.00 €");
lblBrutto.setText("0.00 €");
setAktionenDeaktiviert(); setAktionenDeaktiviert();
btnNeu.setEnabled(true); btnAbbrechen.setEnabled(true);
dokumentTable.clearSelection();
} }
/** private void loeschen() {
* Erzeugt das spec-konforme Folgedokument zum aktuell ausgewählten
* Dokument. PH-DP-04 / PH-DP-06 / PH-DP-08.
*/
private void folgeaktion(DokumentType erwartet) {
if (aktuellesDokument == null) { if (aktuellesDokument == null) {
mainFrame.showStatus("Kein Dokument ausgewählt."); mainFrame.showStatus("Kein Dokument ausgewählt.");
return; return;
} }
if (aktuellesDokument.getTyp() != erwartet) { int confirm = JOptionPane.showConfirmDialog(
mainFrame.showStatus("Aktion passt nicht zum Dokumenttyp."); this, "Dokument wirklich löschen?", "Löschen bestätigen",
return; JOptionPane.YES_NO_OPTION
);
if (confirm != JOptionPane.YES_OPTION) return;
boolean deleted = dokumentenService.deleteDokument(aktuellesDokument.getDokumentNummer()); // FIX
if (deleted) {
ladeDokumente(); neuesFormular();
mainFrame.showStatus("Dokument gelöscht.");
} else {
mainFrame.showStatus("Dokument kann nicht gelöscht werden (z. B. festgeschriebene Rechnung).");
} }
String quellNr = aktuellesDokument.getDokumentNummer(); }
private void folgeaktion(String typ) {
if (aktuellesDokument == null) return;
try { try {
Dokument folge; switch (typ) {
switch (erwartet) { case "ANGEBOT_ZU_AUFTRAG" ->
case ANGEBOT -> dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX
folge = dokumentService.erstelleAuftragsbestaetigung(quellNr); // PH-DP-04 case "AUFTRAG_ZU_LIEFERSCHEIN" ->
case AUFTRAGSBESTAETIGUNG -> dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX
folge = dokumentService.erstelleLieferschein(quellNr, LocalDate.now()); // PH-DP-06 case "LIEFERSCHEIN_ZU_RECHNUNG" ->
case LIEFERSCHEIN -> dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX
folge = dokumentService.erstelleRechnung(quellNr); // PH-DP-08
default -> { mainFrame.showStatus("Kein Folgedokument vorgesehen."); return; }
} }
ladeDokumente(); ladeDokumente();
mainFrame.showStatus("Folgedokument erstellt: " + folge.getDokumentNummer()); mainFrame.showStatus("Folgedokument erfolgreich erstellt.");
} catch (RuntimeException ex) { } catch (Exception ex) {
// Fängt IllegalStateException, DuplicateDocumentException,
// ValidationException, DokumentNotFoundException
mainFrame.showStatus("Fehler: " + ex.getMessage()); mainFrame.showStatus("Fehler: " + ex.getMessage());
} }
} }
@ -427,128 +361,18 @@ public class DokumentPanel extends JPanel {
return; return;
} }
try { try {
Path pfad = dokumentService.exportiereAlsPdf( dokumentenService.exportPdf(
aktuellesDokument.getDokumentNummer()); // PHGUI12 / PH-DP-12 aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX
mainFrame.showStatus("PDF erzeugt: " + pfad); mainFrame.showStatus("PDF-Export erfolgreich.");
} catch (RuntimeException ex) { } catch (Exception ex) {
mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage()); mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage());
} }
} }
// -------------------------------------------------------------------------
// Helfer
// -------------------------------------------------------------------------
private static String typBezeichnung(DokumentType t) {
if (t == null) return "";
return switch (t) {
case ANGEBOT -> "Angebot";
case AUFTRAGSBESTAETIGUNG -> "Auftragsbestätigung";
case LIEFERSCHEIN -> "Lieferschein";
case RECHNUNG -> "Rechnung";
};
}
private static Color farbeFuerStatus(String status) {
return switch (status) {
case "OFFEN", "BESTAETIGT" -> new Color(41, 128, 185);
case "GELIEFERT" -> new Color(39, 174, 96);
case "FESTGESCHRIEBEN" -> new Color(155, 89, 182);
case "UEBERFUEHRT", "FAKTURIERT" -> new Color(127, 140, 141);
default -> Color.BLACK;
};
}
private static String formatProzent(BigDecimal mwstSatz) {
if (mwstSatz == null) return "";
return mwstSatz.multiply(BigDecimal.valueOf(100)) + " %";
}
private JTextField createReadOnlyField() { private JTextField createReadOnlyField() {
JTextField f = new JTextField(); JTextField f = new JTextField();
f.setEditable(false); f.setEditable(false);
f.setBackground(new Color(230, 230, 230)); f.setBackground(new Color(230, 230, 230));
return f; return f;
} }
// =========================================================================
// Adapter / Wrapper GUI-intern, keine Eingriffe in fremde Module
// =========================================================================
/**
* Adapter K3 (PH v1.1 Kap. 6.2): Bedient die von Gruppe F geforderte
* {@link CustomerLookup}-Schnittstelle über das spec-konforme
* {@link KundeService} der Kundenverwaltung.
*/
private static final class KundeCustomerLookup implements CustomerLookup {
private final KundeService kundeService;
KundeCustomerLookup(KundeService kundeService) {
this.kundeService = kundeService;
}
@Override
public Optional<CustomerDto> findCustomer(long kundenId) {
return kundeService.finde(kundenId).map(this::toDto);
}
private CustomerDto toDto(Kunde k) {
String name;
if (k.getFirmenname() != null && !k.getFirmenname().isBlank()) {
name = k.getFirmenname();
} else {
String n = k.getNachname() == null ? "" : k.getNachname();
name = (k.getVorname() == null || k.getVorname().isBlank())
? n : (k.getVorname() + " " + n);
}
String anschrift = String.format("%s, %s %s",
nullAls(k.getStrasse()),
nullAls(k.getPlz()),
nullAls(k.getOrt()));
return new CustomerDto(k.getKundeId(), name, anschrift, nullAls(k.getUstIdNr()));
}
private static String nullAls(String s) { return s == null ? "" : s; }
}
/**
* Decorator über {@link DokumentRepository}: trackt zusätzlich alle
* gespeicherten Dokumente, damit das Panel eine vollständige Liste
* anzeigen kann. {@code findByNummer/findByKunde/...} werden an das
* gekapselte Repository delegiert.
*
* Notwendig, weil das Schnittstellen-Interface kein {@code findAll()}
* vorsieht (Gruppe F PH v1.0 Kap. 6.2) ein Tracking in der GUI ist
* spec-neutral und greift nicht in Gruppe F's Modul ein.
*/
private static final class TrackingDokumentRepository implements DokumentRepository {
private final DokumentRepository delegate;
private final List<Dokument> alle = new ArrayList<>();
TrackingDokumentRepository(DokumentRepository delegate) {
this.delegate = delegate;
}
List<Dokument> findAll() {
List<Dokument> kopie = new ArrayList<>(alle);
kopie.sort(Comparator
.comparing(Dokument::getDatum, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Dokument::getDokumentNummer));
return Collections.unmodifiableList(kopie);
}
@Override
public void save(Dokument dokument) {
delegate.save(dokument);
if (!alle.contains(dokument)) {
alle.add(dokument);
}
}
@Override public Optional<Dokument> findByNummer(String nummer) { return delegate.findByNummer(nummer); }
@Override public List<Dokument> findByKunde(long kundenId) { return delegate.findByKunde(kundenId); }
@Override public boolean existsFolgedokument(String quellDokumentNr) { return delegate.existsFolgedokument(quellDokumentNr); }
@Override public boolean existsReferencedProdukt(long produktId) { return delegate.existsReferencedProdukt(produktId); }
@Override public boolean existsReferencedKunde(long kundenId) { return delegate.existsReferencedKunde(kundenId); }
@Override public int findMaxLaufendeNummer(String praefix, int j) { return delegate.findMaxLaufendeNummer(praefix, j); }
}
} }