Compare commits

...

5 Commits

5 changed files with 1539 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,382 @@
package de.hsmannheim.faktura.gui;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.List;
import de.hsmannheim.faktura.dokumente.domain.Dokument;
import de.hsmannheim.faktura.dokumente.domain.DokumentStatus;
import de.hsmannheim.faktura.dokumente.service.DokumentenService;
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
/**
* Panel für den Dokumentenprozess (PHGUI10, PHGUI11, PHGUI12).
* Zeigt Status und zulässige Folgeaktionen an. Deaktiviert unzulässige Aktionen (PHGUI10).
* Festgeschriebene Rechnungen sind schreibgeschützt (PHGUI11).
*/
public class DokumentPanel extends JPanel {
private final MainFrame mainFrame;
private final DokumentenService dokumentenService;
// Tabelle
private DefaultTableModel tableModel;
private JTable dokumentTable;
// Detailfelder (schreibgeschützt)
private JTextField txtDokumentnummer;
private JTextField txtKundenId;
private JTextField txtDatum;
private JTextField txtStatus;
private JCheckBox chkFestgeschrieben;
// Positionstabelle
private DefaultTableModel posTableModel;
private JTable posTable;
// Summenfelder
private JLabel lblNetto;
private JLabel lblUst;
private JLabel lblBrutto;
// Aktions-Buttons (PHGUI10)
private JButton btnNeu;
private JButton btnSpeichern;
private JButton btnLoeschen;
private JButton btnAbbrechen;
private JButton btnPdfExport;
// Folgeaktions-Buttons
private JButton btnAngebotZuAuftrag;
private JButton btnAuftragZuLieferschein;
private JButton btnLieferscheinZuRechnung;
private Dokument aktuellesDokument = null;
public DokumentPanel(MainFrame mainFrame) {
this.mainFrame = mainFrame;
this.dokumentenService = new DokumentenService(new DokumentRepository());
setLayout(new BorderLayout(8, 8));
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
initComponents();
ladeDokumente();
}
private void initComponents() {
// --- Linke Seite: Dokumentenliste ---
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
leftPanel.setPreferredSize(new Dimension(300, 0));
String[] cols = {"Nr.", "Typ ▲", "Status"};
tableModel = new DefaultTableModel(cols, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
dokumentTable = new JTable(tableModel) {
// Status-Spalte farbig (PHGUI10)
public Component prepareRenderer(TableCellRenderer r, int row, int col) {
Component c = super.prepareRenderer(r, row, col);
if (col == 2) {
String status = (String) getValueAt(row, col);
if (status != null) {
if (status.contains("Offen")) c.setForeground(new Color(41, 128, 185));
else if (status.contains("Erstellt")) 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 {
c.setForeground(Color.BLACK);
}
return c;
}
};
dokumentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Sortierung (PHGUI08)
dokumentTable.setAutoCreateRowSorter(true);
dokumentTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) zeigeAusgewaehltes();
});
leftPanel.add(new JLabel("Dokumente"), BorderLayout.NORTH);
leftPanel.add(new JScrollPane(dokumentTable), BorderLayout.CENTER);
// --- Rechte Seite: Detailansicht ---
JPanel rightPanel = new JPanel(new BorderLayout(4, 8));
// Kopfbereich
JPanel headerPanel = new JPanel(new GridBagLayout());
headerPanel.setBorder(BorderFactory.createTitledBorder("Dokumentdetail"));
GridBagConstraints g = new GridBagConstraints();
g.insets = new Insets(3, 4, 3, 4);
g.fill = GridBagConstraints.HORIZONTAL;
g.weightx = 0.5;
// Festgeschrieben-Checkbox (PHGUI11) oben rechts
chkFestgeschrieben = new JCheckBox("Festgeschrieben");
chkFestgeschrieben.setEnabled(false);
g.gridx = 1; g.gridy = 0; g.anchor = GridBagConstraints.EAST;
headerPanel.add(chkFestgeschrieben, g);
g.anchor = GridBagConstraints.WEST;
g.gridx = 0; g.gridy = 1; headerPanel.add(new JLabel("Dokumentnummer"), g);
g.gridx = 1; headerPanel.add(new JLabel("Kundennummer"), g);
txtDokumentnummer = createReadOnlyField();
txtKundenId = createReadOnlyField();
g.gridx = 0; g.gridy = 2; headerPanel.add(txtDokumentnummer, g);
g.gridx = 1; headerPanel.add(txtKundenId, g);
g.gridx = 0; g.gridy = 3; headerPanel.add(new JLabel("Datum"), g);
g.gridx = 1; headerPanel.add(new JLabel("Status"), g);
txtDatum = createReadOnlyField();
txtStatus = createReadOnlyField();
g.gridx = 0; g.gridy = 4; headerPanel.add(txtDatum, g);
g.gridx = 1; headerPanel.add(txtStatus, g);
rightPanel.add(headerPanel, BorderLayout.NORTH);
// Positionstabelle
String[] posCols = {"Pos", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Summe"};
posTableModel = new DefaultTableModel(posCols, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
posTable = new JTable(posTableModel);
posTable.setEnabled(false);
JPanel posPanel = new JPanel(new BorderLayout());
posPanel.setBorder(BorderFactory.createTitledBorder("Positionen:"));
posPanel.add(new JScrollPane(posTable), BorderLayout.CENTER);
// Summen
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. 19%:")); summenPanel.add(lblUst);
summenPanel.add(new JLabel("Brutto:")); summenPanel.add(lblBrutto);
JPanel mittelPanel = new JPanel(new BorderLayout());
mittelPanel.add(posPanel, BorderLayout.CENTER);
mittelPanel.add(summenPanel, BorderLayout.SOUTH);
rightPanel.add(mittelPanel, BorderLayout.CENTER);
// --- 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);
// Folgeaktionen
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());
btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); });
btnPdfExport.addActionListener(e -> pdfExport()); // PHGUI12
btnLoeschen.addActionListener(e -> loeschen());
btnAngebotZuAuftrag.addActionListener(e -> folgeaktion("ANGEBOT_ZU_AUFTRAG"));
btnAuftragZuLieferschein.addActionListener(e -> folgeaktion("AUFTRAG_ZU_LIEFERSCHEIN"));
btnLieferscheinZuRechnung.addActionListener(e -> folgeaktion("LIEFERSCHEIN_ZU_RECHNUNG"));
// Initial alle deaktivieren (PHGUI10)
setAktionenDeaktiviert();
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
split.setDividerLocation(300);
add(split, BorderLayout.CENTER);
}
// --- Daten ---
private void ladeDokumente() {
tableModel.setRowCount(0);
List<Dokument> alle = dokumentenService.getAllDokumente();
for (Dokument d : alle) {
tableModel.addRow(new Object[]{
d.getDokumentnummer(),
d.getTypBezeichnung(),
d.getStatus().getAnzeigename()
});
}
}
private void zeigeAusgewaehltes() {
int row = dokumentTable.getSelectedRow();
if (row < 0) return;
int modelRow = dokumentTable.convertRowIndexToModel(row);
String nr = (String) tableModel.getValueAt(modelRow, 0);
dokumentenService.getDokumentByNummer(nr).ifPresent(d -> {
aktuellesDokument = d;
txtDokumentnummer.setText(d.getDokumentnummer());
txtKundenId.setText(String.valueOf(d.getKundeId()));
txtDatum.setText(d.getDatum().toString());
txtStatus.setText(d.getStatus().getAnzeigename());
boolean festgeschrieben = d.isFestgeschrieben();
chkFestgeschrieben.setSelected(festgeschrieben);
// Positionen laden
posTableModel.setRowCount(0);
int pos = 1;
for (var position : d.getPositionen()) {
posTableModel.addRow(new Object[]{
pos++,
position.getProduktbezeichnung(),
position.getMenge(),
position.getEinzelpreisNetto() + " €",
position.getMehrwertsteuerSatz() + "%",
position.getSumme() + " €"
});
}
// Summen
if (d.getNettosumme() != null) {
lblNetto.setText(d.getNettosumme().toPlainString() + " €");
lblUst.setText(d.getUstSumme().toPlainString() + " €");
lblBrutto.setText(d.getBruttosumme().toPlainString() + " €");
}
// PHGUI10: Zulässige Folgeaktionen setzen
aktualisiereAktionen(d);
});
}
/**
* Aktiviert nur die zulässigen Folgeaktionen basierend auf dem Dokumentstatus (PHGUI10).
* Festgeschriebene Rechnungen: alle Bearbeitungsaktionen deaktiviert (PHGUI11).
*/
private void aktualisiereAktionen(Dokument d) {
DokumentStatus status = d.getStatus();
boolean festgeschrieben = d.isFestgeschrieben();
// PHGUI11: Festgeschriebene Rechnung → alles deaktiviert außer PDF-Export
if (festgeschrieben) {
btnNeu.setEnabled(true);
btnSpeichern.setEnabled(false);
btnLoeschen.setEnabled(false);
btnAbbrechen.setEnabled(true);
btnPdfExport.setEnabled(true); // PHGUI12
btnAngebotZuAuftrag.setEnabled(false);
btnAuftragZuLieferschein.setEnabled(false);
btnLieferscheinZuRechnung.setEnabled(false);
return;
}
// PHGUI10: Folgeaktionen je nach Status
btnNeu.setEnabled(true);
btnSpeichern.setEnabled(true);
btnLoeschen.setEnabled(true);
btnAbbrechen.setEnabled(true);
btnPdfExport.setEnabled(status == DokumentStatus.RECHNUNG_ERSTELLT);
btnAngebotZuAuftrag.setEnabled(status == DokumentStatus.ANGEBOT_OFFEN);
btnAuftragZuLieferschein.setEnabled(status == DokumentStatus.AUFTRAGSBESTAETIGUNG_ERSTELLT);
btnLieferscheinZuRechnung.setEnabled(status == DokumentStatus.LIEFERSCHEIN_ERSTELLT);
}
private void setAktionenDeaktiviert() {
btnSpeichern.setEnabled(false);
btnLoeschen.setEnabled(false);
btnPdfExport.setEnabled(false);
btnAngebotZuAuftrag.setEnabled(false);
btnAuftragZuLieferschein.setEnabled(false);
btnLieferscheinZuRechnung.setEnabled(false);
}
private void neuesFormular() {
aktuellesDokument = null;
txtDokumentnummer.setText(""); txtKundenId.setText("");
txtDatum.setText(""); txtStatus.setText("");
chkFestgeschrieben.setSelected(false);
posTableModel.setRowCount(0);
lblNetto.setText("0,00 €"); lblUst.setText("0,00 €"); lblBrutto.setText("0,00 €");
setAktionenDeaktiviert();
btnNeu.setEnabled(true); btnAbbrechen.setEnabled(true);
dokumentTable.clearSelection();
}
private void loeschen() {
if (aktuellesDokument == null) {
mainFrame.showStatus("Kein Dokument ausgewählt.");
return;
}
// PHGUI07: Bestätigungsdialog
int confirm = JOptionPane.showConfirmDialog(
this, "Dokument wirklich löschen?", "Löschen bestätigen",
JOptionPane.YES_NO_OPTION
);
if (confirm != JOptionPane.YES_OPTION) return;
boolean deleted = dokumentenService.deleteDokument(aktuellesDokument.getDokumentnummer());
if (deleted) {
ladeDokumente(); neuesFormular();
mainFrame.showStatus("Dokument gelöscht.");
} else {
mainFrame.showStatus("Dokument kann nicht gelöscht werden.");
}
}
private void folgeaktion(String typ) {
if (aktuellesDokument == null) return;
try {
switch (typ) {
case "ANGEBOT_ZU_AUFTRAG" ->
dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentnummer());
case "AUFTRAG_ZU_LIEFERSCHEIN" ->
dokumentenService.createLieferschein(aktuellesDokument.getDokumentnummer());
case "LIEFERSCHEIN_ZU_RECHNUNG" ->
dokumentenService.createRechnung(aktuellesDokument.getDokumentnummer());
}
ladeDokumente();
mainFrame.showStatus("Folgedokument erfolgreich erstellt.");
} catch (Exception ex) {
mainFrame.showStatus("Fehler: " + ex.getMessage());
}
}
private void pdfExport() {
// PHGUI12: PDF-Export auslösen
if (aktuellesDokument == null) {
mainFrame.showStatus("Kein Dokument ausgewählt.");
return;
}
try {
dokumentenService.exportPdf(aktuellesDokument.getDokumentnummer(), aktuellesDokument.getTyp());
mainFrame.showStatus("PDF-Export erfolgreich gestartet.");
} catch (Exception ex) {
mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage());
}
}
private JTextField createReadOnlyField() {
JTextField f = new JTextField();
f.setEditable(false);
f.setBackground(new Color(230, 230, 230));
return f;
}
}

View File

@ -0,0 +1,351 @@
package de.hsmannheim.faktura.gui;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.List;
import de.hsmannheim.faktura.kunde.Kunde;
import de.hsmannheim.faktura.kunde.service.KundenService;
import de.hsmannheim.faktura.kunde.repository.KundenRepository;
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
/**
* Panel für die Kundenverwaltung (PHGUI02, PHGUI03, PHGUI05PHGUI09).
* Einheitliches Layout mit Navigationsbereich, Inhaltsbereich und Statusbereich (PHGUI02).
*/
public class KundenPanel extends JPanel {
private final MainFrame mainFrame;
private final KundenService kundenService;
// Tabelle
private DefaultTableModel tableModel;
private JTable kundenTable;
// Formularfelder
private JTextField txtKundenId;
private JTextField txtFirmenname;
private JTextField txtStrasse;
private JTextField txtPlz;
private JTextField txtOrt;
private JTextField txtTelefon;
private JTextField txtEmail;
private JTextField txtUstIdNr;
// Fehlerhinweise (PHGUI03, PHGUI05)
private JLabel lblFirmenfehler;
private JLabel lblStrasseFehler;
private JLabel lblPlzFehler;
private JLabel lblOrtFehler;
private JLabel lblEmailFehler;
// Suche (PHGUI09)
private JTextField txtSuche;
// Buttons (PHGUI02)
private JButton btnNeu;
private JButton btnSpeichern;
private JButton btnLoeschen;
private JButton btnAbbrechen;
public KundenPanel(MainFrame mainFrame) {
this.mainFrame = mainFrame;
this.kundenService = new KundenService(
new KundenRepository(),
createDummyDokumentRepository()
);
setLayout(new BorderLayout(8, 8));
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
initComponents();
ladeKunden();
}
private void initComponents() {
// --- Linke Seite: Liste + Suche ---
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
leftPanel.setPreferredSize(new Dimension(320, 0));
String[] columns = {"ID", "Name ▲", "Ort"};
tableModel = new DefaultTableModel(columns, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
kundenTable = new JTable(tableModel);
kundenTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Sortierung (PHGUI08)
kundenTable.setAutoCreateRowSorter(true);
kundenTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) zeigeAusgewaehlten();
});
leftPanel.add(new JLabel("Kundenliste"), BorderLayout.NORTH);
leftPanel.add(new JScrollPane(kundenTable), BorderLayout.CENTER);
// Suche (PHGUI09: nach Name oder Kunden-ID)
JPanel suchePanel = new JPanel(new BorderLayout(4, 0));
suchePanel.setBorder(BorderFactory.createTitledBorder("↑ Suche (Name / Kunden-ID)"));
txtSuche = new JTextField();
txtSuche.setToolTipText("Name oder ID eingeben...");
txtSuche.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
public void insertUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
public void removeUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
public void changedUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
});
suchePanel.add(txtSuche, BorderLayout.CENTER);
leftPanel.add(suchePanel, BorderLayout.SOUTH);
// --- Rechte Seite: Formular ---
JPanel rightPanel = new JPanel(new BorderLayout(4, 8));
rightPanel.setBorder(BorderFactory.createTitledBorder("Kundendetails Pflichtfelder sind mit * markiert"));
JPanel formPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(3, 4, 1, 4);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 0.5;
// Zeile 0: KundenId + Telefon
addLabel(formPanel, gbc, "Kunden-ID (autom.)", 0, 0);
addLabel(formPanel, gbc, "Telefonnummer", 1, 0);
txtKundenId = new JTextField(); txtKundenId.setEditable(false);
txtKundenId.setBackground(new Color(230, 230, 230));
addField(formPanel, gbc, txtKundenId, 0, 1);
txtTelefon = new JTextField();
addField(formPanel, gbc, txtTelefon, 1, 1);
// Zeile 2: Firmenname + E-Mail
addLabel(formPanel, gbc, "Firmenname / Nachname *", 0, 2);
addLabel(formPanel, gbc, "E-Mail", 1, 2);
txtFirmenname = new JTextField();
addField(formPanel, gbc, txtFirmenname, 0, 3);
txtEmail = new JTextField();
addField(formPanel, gbc, txtEmail, 1, 3);
// Fehler Firmenname + E-Mail
lblFirmenfehler = createFehlerLabel();
addField(formPanel, gbc, lblFirmenfehler, 0, 4);
lblEmailFehler = createFehlerLabel();
lblEmailFehler.setText("▲ Format: name@domain.tld");
lblEmailFehler.setForeground(new Color(120, 120, 120));
addField(formPanel, gbc, lblEmailFehler, 1, 4);
// Zeile 5: Straße + UStIdNr
addLabel(formPanel, gbc, "Straße *", 0, 5);
addLabel(formPanel, gbc, "UStIdNr.", 1, 5);
txtStrasse = new JTextField();
addField(formPanel, gbc, txtStrasse, 0, 6);
txtUstIdNr = new JTextField();
addField(formPanel, gbc, txtUstIdNr, 1, 6);
lblStrasseFehler = createFehlerLabel();
addField(formPanel, gbc, lblStrasseFehler, 0, 7);
// Zeile 8: PLZ
addLabel(formPanel, gbc, "PLZ *", 0, 8);
txtPlz = new JTextField();
addField(formPanel, gbc, txtPlz, 0, 9);
lblPlzFehler = createFehlerLabel();
addField(formPanel, gbc, lblPlzFehler, 0, 10);
// Zeile 11: Ort
addLabel(formPanel, gbc, "Ort *", 0, 11);
txtOrt = new JTextField();
addField(formPanel, gbc, txtOrt, 0, 12);
lblOrtFehler = createFehlerLabel();
addField(formPanel, gbc, lblOrtFehler, 0, 13);
rightPanel.add(new JScrollPane(formPanel), BorderLayout.CENTER);
// --- Buttons (PHGUI02) ---
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
btnNeu = new JButton("Neu");
btnSpeichern = new JButton("Speichern");
btnLoeschen = new JButton("Löschen");
btnAbbrechen = new JButton("Abbrechen");
btnPanel.add(btnNeu);
btnPanel.add(btnSpeichern);
btnPanel.add(btnLoeschen);
btnPanel.add(btnAbbrechen);
rightPanel.add(btnPanel, BorderLayout.SOUTH);
btnNeu.addActionListener(e -> neuesFormular());
btnSpeichern.addActionListener(e -> speichern());
btnLoeschen.addActionListener(e -> loeschen());
btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeKunden(); });
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
split.setDividerLocation(320);
add(split, BorderLayout.CENTER);
}
// --- Hilfsmethoden Layout ---
private void addLabel(JPanel p, GridBagConstraints g, String text, int col, int row) {
g.gridx = col; g.gridy = row; g.weighty = 0;
p.add(new JLabel(text), g);
}
private void addField(JPanel p, GridBagConstraints g, JComponent c, int col, int row) {
g.gridx = col; g.gridy = row; g.weighty = 0;
p.add(c, g);
}
// --- Daten ---
private void ladeKunden() {
ladeKundenListe(kundenService.getAllKunden());
}
private void ladeKundenListe(List<Kunde> liste) {
tableModel.setRowCount(0);
for (Kunde k : liste) {
tableModel.addRow(new Object[]{
String.valueOf(k.getKundenId()),
k.getFirmennameNachname(),
k.getOrt()
});
}
}
private void sucheAktualisieren() {
// PHGUI09: Groß-/Kleinschreibung ignorieren, nach Name oder ID suchen
String query = txtSuche.getText();
ladeKundenListe(kundenService.searchKunden(query));
}
private void zeigeAusgewaehlten() {
int row = kundenTable.getSelectedRow();
if (row < 0) return;
int modelRow = kundenTable.convertRowIndexToModel(row);
String idStr = (String) tableModel.getValueAt(modelRow, 0);
kundenService.getKundeById(Integer.parseInt(idStr)).ifPresent(k -> {
txtKundenId.setText(String.valueOf(k.getKundenId()));
txtFirmenname.setText(k.getFirmennameNachname());
txtStrasse.setText(k.getStrasse());
txtPlz.setText(k.getPlz());
txtOrt.setText(k.getOrt());
txtTelefon.setText(k.getTelefonnummer());
txtEmail.setText(k.getEmail());
txtUstIdNr.setText(k.getUstIdNr());
clearFehler();
});
}
// --- Aktionen ---
private void neuesFormular() {
txtKundenId.setText(""); txtFirmenname.setText(""); txtStrasse.setText("");
txtPlz.setText(""); txtOrt.setText(""); txtTelefon.setText("");
txtEmail.setText(""); txtUstIdNr.setText("");
clearFehler();
kundenTable.clearSelection();
}
private void speichern() {
clearFehler();
boolean valid = true;
// PHGUI03: Pflichtfelder
if (txtFirmenname.getText().trim().isEmpty()) {
lblFirmenfehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
}
if (txtStrasse.getText().trim().isEmpty()) {
lblStrasseFehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
}
if (txtPlz.getText().trim().isEmpty()) {
lblPlzFehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
}
if (txtOrt.getText().trim().isEmpty()) {
lblOrtFehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
}
// PHGUI05: E-Mail-Validierung (Format: name@domain.tld)
String email = txtEmail.getText().trim();
if (!email.isEmpty() && !email.matches("^[^@]+@[^@]+\\.[^@]+$")) {
lblEmailFehler.setText("⚠ Format: name@domain.tld");
lblEmailFehler.setForeground(new Color(192, 57, 43));
valid = false;
}
if (!valid) return;
int kundenId = txtKundenId.getText().trim().isEmpty() ? 0
: Integer.parseInt(txtKundenId.getText().trim());
Kunde k = new Kunde(
kundenId,
txtFirmenname.getText().trim(),
txtStrasse.getText().trim(),
txtPlz.getText().trim(),
txtOrt.getText().trim(),
txtTelefon.getText().trim(),
email,
txtUstIdNr.getText().trim()
);
try {
kundenService.saveKunde(k);
ladeKunden();
neuesFormular();
// PHGUI06: Bestätigung < 1 Sekunde
mainFrame.showStatus("Kunde erfolgreich gespeichert.");
} catch (IllegalArgumentException ex) {
mainFrame.showStatus("Fehler: " + ex.getMessage());
}
}
private void loeschen() {
int row = kundenTable.getSelectedRow();
if (row < 0) {
mainFrame.showStatus("Kein Kunde ausgewählt.");
return;
}
// PHGUI07: Bestätigungsdialog
int confirm = JOptionPane.showConfirmDialog(
this, "Kunden wirklich löschen?", "Löschen bestätigen",
JOptionPane.YES_NO_OPTION
);
if (confirm != JOptionPane.YES_OPTION) return;
int modelRow = kundenTable.convertRowIndexToModel(row);
String idStr = (String) tableModel.getValueAt(modelRow, 0);
boolean deleted = kundenService.deleteKunde(Integer.parseInt(idStr));
if (deleted) {
ladeKunden();
neuesFormular();
mainFrame.showStatus("Kunde gelöscht.");
} else {
mainFrame.showStatus("Löschen nicht möglich Kunde wird in Dokumenten verwendet.");
}
}
private void clearFehler() {
lblFirmenfehler.setText("");
lblStrasseFehler.setText("");
lblPlzFehler.setText("");
lblOrtFehler.setText("");
lblEmailFehler.setText("▲ Format: name@domain.tld");
lblEmailFehler.setForeground(new Color(120, 120, 120));
}
private JLabel createFehlerLabel() {
JLabel lbl = new JLabel("");
lbl.setForeground(new Color(192, 57, 43));
lbl.setFont(new Font("SansSerif", Font.PLAIN, 11));
return lbl;
}
private DokumentRepository createDummyDokumentRepository() {
return new DokumentRepository() {
public boolean existsReferencedProdukt(long id) { return false; }
public boolean existsReferencedKunde(long id) { return false; }
public int findMaxLaufendeNummer(String p, int j) { return 0; }
public void save(de.hsmannheim.faktura.dokumente.domain.Dokument d) {}
public java.util.Optional<de.hsmannheim.faktura.dokumente.domain.Dokument> findByNummer(String n) { return java.util.Optional.empty(); }
public List<de.hsmannheim.faktura.dokumente.domain.Dokument> findByKunde(long id) { return List.of(); }
public boolean existsFolgedokument(String q) { return false; }
};
}
}

View File

@ -0,0 +1,104 @@
package de.hsmannheim.faktura.gui;
import javax.swing.*;
import java.awt.*;
/**
* Hauptfenster der Anwendung (PHGUI01, PHGUI02).
* Stellt die dauerhaft sichtbare Hauptnavigation bereit und lädt die Modulpanels.
*/
public class MainFrame extends JFrame {
private final ProduktPanel produktPanel;
private final KundenPanel kundenPanel;
private final DokumentPanel dokumentPanel;
private final JLabel statusLabel;
public MainFrame() {
setTitle("Fakturierungssystem");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1100, 700);
setLocationRelativeTo(null);
// Hauptlayout
setLayout(new BorderLayout());
// --- Navigation (PHGUI01) ---
JPanel navPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
navPanel.setBackground(new Color(52, 73, 94));
JLabel title = new JLabel(" Fakturierungssystem ");
title.setForeground(Color.WHITE);
title.setFont(new Font("SansSerif", Font.BOLD, 14));
navPanel.add(title);
JButton btnProdukt = createNavButton("Produktverwaltung");
JButton btnKunde = createNavButton("Kundenverwaltung");
JButton btnDokument = createNavButton("Dokumentenprozess");
navPanel.add(btnProdukt);
navPanel.add(btnKunde);
navPanel.add(btnDokument);
add(navPanel, BorderLayout.NORTH);
// --- Inhaltsbereich ---
JPanel contentArea = new JPanel(new CardLayout());
produktPanel = new ProduktPanel(this);
kundenPanel = new KundenPanel(this);
dokumentPanel = new DokumentPanel(this);
contentArea.add(produktPanel, "produkt");
contentArea.add(kundenPanel, "kunde");
contentArea.add(dokumentPanel, "dokument");
add(contentArea, BorderLayout.CENTER);
// --- Statusbereich (PHGUI06) ---
statusLabel = new JLabel("Status: Bereit");
statusLabel.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
statusLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
add(statusLabel, BorderLayout.SOUTH);
// Navigation-Actions (PHGUI01: 1 Klick)
CardLayout cl = (CardLayout) contentArea.getLayout();
btnProdukt.addActionListener(e -> cl.show(contentArea, "produkt"));
btnKunde.addActionListener(e -> cl.show(contentArea, "kunde"));
btnDokument.addActionListener(e -> cl.show(contentArea, "dokument"));
// Standard: Produktverwaltung
cl.show(contentArea, "produkt");
setVisible(true);
}
/**
* Zeigt eine Statusmeldung im Statusbereich an (PHGUI06: innerhalb 1 Sekunde).
*/
public void showStatus(String message) {
statusLabel.setText("Status: " + message);
// Reset nach 3 Sekunden
Timer timer = new Timer(3000, e -> statusLabel.setText("Status: Bereit"));
timer.setRepeats(false);
timer.start();
}
private JButton createNavButton(String text) {
JButton btn = new JButton(text);
btn.setBackground(new Color(52, 73, 94));
btn.setForeground(Color.WHITE);
btn.setFocusPainted(false);
btn.setBorderPainted(false);
btn.setFont(new Font("SansSerif", Font.BOLD, 13));
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
btn.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent e) {
btn.setBackground(new Color(41, 128, 185));
}
public void mouseExited(java.awt.event.MouseEvent e) {
btn.setBackground(new Color(52, 73, 94));
}
});
return btn;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MainFrame::new);
}
}

View File

@ -0,0 +1,342 @@
package de.hsmannheim.faktura.gui;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.math.BigDecimal;
import java.util.List;
import de.hsmannheim.faktura.product.MwstSatz;
import de.hsmannheim.faktura.product.Product;
import de.hsmannheim.faktura.product.service.ProductService;
import de.hsmannheim.faktura.product.repository.ProductRepository;
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
/**
* Panel für die Produktverwaltung (PHGUI01PHGUI04, PHGUI06PHGUI09).
* Einheitliches Layout mit Navigationsbereich, Inhaltsbereich und Statusbereich (PHGUI02).
*/
public class ProduktPanel extends JPanel {
private final MainFrame mainFrame;
private final ProductService productService;
// Tabelle
private DefaultTableModel tableModel;
private JTable produktTable;
// Formularfelder
private JTextField txtProduktId;
private JTextField txtBezeichnung;
private JTextField txtPreis;
private JComboBox<MwstSatz> cmbMwst;
private JTextArea txtBeschreibung;
private JTextField txtKategorie;
// Fehlerhinweise (PHGUI03)
private JLabel lblBezeichnungFehler;
private JLabel lblPreisFehler;
// Suche (PHGUI09)
private JTextField txtSuche;
// Buttons (PHGUI02: einheitlich beschriftet)
private JButton btnNeu;
private JButton btnSpeichern;
private JButton btnLoeschen;
private JButton btnAbbrechen;
public ProduktPanel(MainFrame mainFrame) {
this.mainFrame = mainFrame;
this.productService = new ProductService(
new ProductRepository(),
createDummyDokumentRepository()
);
setLayout(new BorderLayout(8, 8));
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
initComponents();
ladeProdukte();
}
private void initComponents() {
// --- Linke Seite: Liste + Suche ---
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
leftPanel.setPreferredSize(new Dimension(320, 0));
String[] columns = {"ID", "Bezeichnung ▲", "Preis"};
tableModel = new DefaultTableModel(columns, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
produktTable = new JTable(tableModel);
produktTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
produktTable.getTableHeader().setReorderingAllowed(false);
// Sortierung (PHGUI08)
produktTable.setAutoCreateRowSorter(true);
produktTable.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) zeigeAusgewaehltes();
});
leftPanel.add(new JLabel("Produktliste"), BorderLayout.NORTH);
leftPanel.add(new JScrollPane(produktTable), BorderLayout.CENTER);
// Suche (PHGUI09)
JPanel suchePanel = new JPanel(new BorderLayout(4, 0));
suchePanel.setBorder(BorderFactory.createTitledBorder("↑ Suche"));
txtSuche = new JTextField();
txtSuche.setToolTipText("Bezeichnung eingeben...");
txtSuche.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
public void insertUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
public void removeUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
public void changedUpdate(javax.swing.event.DocumentEvent e) { sucheAktualisieren(); }
});
suchePanel.add(new JLabel("Bezeichnung:"), BorderLayout.WEST);
suchePanel.add(txtSuche, BorderLayout.CENTER);
leftPanel.add(suchePanel, BorderLayout.SOUTH);
// --- Rechte Seite: Formular ---
JPanel rightPanel = new JPanel(new BorderLayout(4, 8));
rightPanel.setBorder(BorderFactory.createTitledBorder("Produktdetails Pflichtfelder sind mit * markiert"));
JPanel formPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 2, 4);
gbc.fill = GridBagConstraints.HORIZONTAL;
// Zeile 0: ProduktId + Einzelpreis
gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.5;
formPanel.add(new JLabel("Produkt-ID (autom. vergeben)"), gbc);
gbc.gridx = 1;
formPanel.add(new JLabel("Einzelpreis Netto (€) *"), gbc);
gbc.gridx = 0; gbc.gridy = 1;
txtProduktId = new JTextField();
txtProduktId.setEditable(false);
txtProduktId.setBackground(new Color(230, 230, 230));
formPanel.add(txtProduktId, gbc);
gbc.gridx = 1;
txtPreis = new JTextField();
formPanel.add(txtPreis, gbc);
// Fehlerhinweis Preis
gbc.gridx = 1; gbc.gridy = 2;
lblPreisFehler = createFehlerLabel();
formPanel.add(lblPreisFehler, gbc);
// Zeile 3: Bezeichnung + MwSt
gbc.gridx = 0; gbc.gridy = 3;
formPanel.add(new JLabel("Bezeichnung *"), gbc);
gbc.gridx = 1;
formPanel.add(new JLabel("MwSt.-Satz (%) *"), gbc);
gbc.gridx = 0; gbc.gridy = 4;
txtBezeichnung = new JTextField();
formPanel.add(txtBezeichnung, gbc);
gbc.gridx = 1;
cmbMwst = new JComboBox<>(MwstSatz.values());
formPanel.add(cmbMwst, gbc);
// Fehlerhinweis Bezeichnung
gbc.gridx = 0; gbc.gridy = 5;
lblBezeichnungFehler = createFehlerLabel();
formPanel.add(lblBezeichnungFehler, gbc);
// Zeile 6: Kategorie
gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2;
formPanel.add(new JLabel("Kategorie"), gbc);
gbc.gridy = 7;
txtKategorie = new JTextField();
formPanel.add(txtKategorie, gbc);
// Zeile 8: Beschreibung
gbc.gridy = 8;
formPanel.add(new JLabel("Beschreibung"), gbc);
gbc.gridy = 9; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH;
txtBeschreibung = new JTextArea(4, 20);
txtBeschreibung.setLineWrap(true);
formPanel.add(new JScrollPane(txtBeschreibung), gbc);
rightPanel.add(formPanel, BorderLayout.CENTER);
// --- Buttons (PHGUI02: einheitlich) ---
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
btnNeu = new JButton("Neu");
btnSpeichern = new JButton("Speichern");
btnLoeschen = new JButton("Löschen");
btnAbbrechen = new JButton("Abbrechen");
btnPanel.add(btnNeu);
btnPanel.add(btnSpeichern);
btnPanel.add(btnLoeschen);
btnPanel.add(btnAbbrechen);
rightPanel.add(btnPanel, BorderLayout.SOUTH);
// Button-Actions
btnNeu.addActionListener(e -> neuesFormular());
btnSpeichern.addActionListener(e -> speichern());
btnLoeschen.addActionListener(e -> loeschen());
btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeProdukte(); });
// Split
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
split.setDividerLocation(320);
add(split, BorderLayout.CENTER);
}
// --- Daten laden ---
private void ladeProdukte() {
ladeProdukteListe(productService.getAllProducts());
}
private void ladeProdukteListe(List<Product> liste) {
tableModel.setRowCount(0);
for (Product p : liste) {
tableModel.addRow(new Object[]{
p.getProductId(),
p.getBezeichnung(),
p.getEinzelpreisNetto() + " €"
});
}
}
private void sucheAktualisieren() {
String query = txtSuche.getText();
// PHGUI09: Groß-/Kleinschreibung ignorieren
ladeProdukteListe(productService.searchProducts(query));
}
private void zeigeAusgewaehltes() {
int row = produktTable.getSelectedRow();
if (row < 0) return;
int modelRow = produktTable.convertRowIndexToModel(row);
String id = (String) tableModel.getValueAt(modelRow, 0);
productService.getProductById(id).ifPresent(p -> {
txtProduktId.setText(p.getProductId());
txtBezeichnung.setText(p.getBezeichnung());
txtPreis.setText(p.getEinzelpreisNetto().toPlainString());
cmbMwst.setSelectedItem(p.getMwstSatz());
txtBeschreibung.setText(p.getBeschreibung());
txtKategorie.setText(p.getKategorie());
clearFehler();
});
}
// --- Aktionen ---
private void neuesFormular() {
txtProduktId.setText("");
txtBezeichnung.setText("");
txtPreis.setText("");
txtBeschreibung.setText("");
txtKategorie.setText("");
cmbMwst.setSelectedIndex(0);
clearFehler();
produktTable.clearSelection();
}
private void speichern() {
clearFehler();
boolean valid = true;
String bezeichnung = txtBezeichnung.getText().trim();
String preisText = txtPreis.getText().trim();
// PHGUI03: Pflichtfeldprüfung
if (bezeichnung.isEmpty()) {
lblBezeichnungFehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
}
BigDecimal preis = null;
if (preisText.isEmpty()) {
lblPreisFehler.setText("⚠ Pflichtfeld darf nicht leer sein");
valid = false;
} else {
try {
preis = new BigDecimal(preisText.replace(",", "."));
// PHGUI04: Negativpreis ablehnen
if (preis.compareTo(BigDecimal.ZERO) < 0) {
lblPreisFehler.setText("⚠ Preis darf nicht negativ sein");
valid = false;
}
} catch (NumberFormatException ex) {
lblPreisFehler.setText("⚠ Ungültiger Zahlenwert");
valid = false;
}
}
if (!valid) return;
Product p = new Product(
txtProduktId.getText().trim(),
bezeichnung,
preis,
(MwstSatz) cmbMwst.getSelectedItem(),
txtBeschreibung.getText().trim(),
txtKategorie.getText().trim()
);
try {
productService.saveProduct(p);
ladeProdukte();
neuesFormular();
// PHGUI06: Sichtbare Bestätigung < 1 Sekunde
mainFrame.showStatus("Produkt erfolgreich gespeichert.");
} catch (IllegalArgumentException ex) {
mainFrame.showStatus("Fehler: " + ex.getMessage());
}
}
private void loeschen() {
int row = produktTable.getSelectedRow();
if (row < 0) {
mainFrame.showStatus("Kein Produkt ausgewählt.");
return;
}
// PHGUI07: Bestätigungsdialog
int confirm = JOptionPane.showConfirmDialog(
this,
"Produkt wirklich löschen?",
"Löschen bestätigen",
JOptionPane.YES_NO_OPTION
);
if (confirm != JOptionPane.YES_OPTION) return;
int modelRow = produktTable.convertRowIndexToModel(row);
String id = (String) tableModel.getValueAt(modelRow, 0);
boolean deleted = productService.deleteProduct(id);
if (deleted) {
ladeProdukte();
neuesFormular();
// PHGUI06
mainFrame.showStatus("Produkt gelöscht.");
} else {
mainFrame.showStatus("Löschen nicht möglich Produkt wird in Dokumenten verwendet.");
}
}
private void clearFehler() {
lblBezeichnungFehler.setText("");
lblPreisFehler.setText("");
}
private JLabel createFehlerLabel() {
JLabel lbl = new JLabel("");
lbl.setForeground(new Color(192, 57, 43));
lbl.setFont(new Font("SansSerif", Font.PLAIN, 11));
return lbl;
}
private DokumentRepository createDummyDokumentRepository() {
return new DokumentRepository() {
public boolean existsReferencedProdukt(long id) { return false; }
public boolean existsReferencedKunde(long id) { return false; }
public int findMaxLaufendeNummer(String p, int j) { return 0; }
public void save(de.hsmannheim.faktura.dokumente.domain.Dokument d) {}
public java.util.Optional<de.hsmannheim.faktura.dokumente.domain.Dokument> findByNummer(String n) { return java.util.Optional.empty(); }
public List<de.hsmannheim.faktura.dokumente.domain.Dokument> findByKunde(long id) { return List.of(); }
public boolean existsFolgedokument(String q) { return false; }
};
}
}