diff --git a/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java b/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java new file mode 100644 index 0000000..3279f71 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java @@ -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 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; + } +} diff --git a/src/main/java/de/hsmannheim/faktura/gui/KundenPanel.java b/src/main/java/de/hsmannheim/faktura/gui/KundenPanel.java new file mode 100644 index 0000000..89b927c --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/gui/KundenPanel.java @@ -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, PHGUI05–PHGUI09). + * 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 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 findByNummer(String n) { return java.util.Optional.empty(); } + public List findByKunde(long id) { return List.of(); } + public boolean existsFolgedokument(String q) { return false; } + }; + } +} diff --git a/src/main/java/de/hsmannheim/faktura/gui/MainFrame.java b/src/main/java/de/hsmannheim/faktura/gui/MainFrame.java new file mode 100644 index 0000000..99fd805 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/gui/MainFrame.java @@ -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); + } +} diff --git a/src/main/java/de/hsmannheim/faktura/gui/ProduktPanel.java b/src/main/java/de/hsmannheim/faktura/gui/ProduktPanel.java new file mode 100644 index 0000000..d062ca0 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/gui/ProduktPanel.java @@ -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 (PHGUI01–PHGUI04, PHGUI06–PHGUI09). + * 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 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 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 findByNummer(String n) { return java.util.Optional.empty(); } + public List findByKunde(long id) { return List.of(); } + public boolean existsFolgedokument(String q) { return false; } + }; + } +}