Compare commits
2 Commits
e86071a403
...
f925d26c86
| Author | SHA1 | Date |
|---|---|---|
|
|
f925d26c86 | |
|
|
2483d51cb7 |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"kunden" : [ {
|
||||
"kundeId" : 1,
|
||||
"firmenname" : "Aura GmBH & Co KG",
|
||||
"nachname" : "Patric",
|
||||
"vorname" : "Nachname",
|
||||
"strasse" : "Muster Straße, 2",
|
||||
"plz" : "67480",
|
||||
"ort" : "Edenkoben",
|
||||
"telefon" : "7265389761",
|
||||
"email" : "Aura@Email.com",
|
||||
"ustIdNr" : "28976398",
|
||||
"lieferadresse" : null,
|
||||
"ansprechpartner" : null
|
||||
} ],
|
||||
"naechsteId" : 2
|
||||
}
|
||||
|
|
@ -5,4 +5,11 @@
|
|||
"mwstSatz" : "NORMAL",
|
||||
"beschreibung" : "Schlagbohrer",
|
||||
"kategorie" : "Elektrowerkzeuge"
|
||||
}, {
|
||||
"productId" : "PROD-dca63cf9",
|
||||
"bezeichnung" : "Schraubenschlüssel",
|
||||
"einzelpreisNetto" : 42.33,
|
||||
"mwstSatz" : "NORMAL",
|
||||
"beschreibung" : "Test",
|
||||
"kategorie" : "Werkzeug"
|
||||
} ]
|
||||
|
|
@ -38,9 +38,9 @@ public class DokumentServiceImpl implements DokumentService {
|
|||
* Alle Abhängigkeiten werden injiziert (Dependency Injection).
|
||||
*/
|
||||
public DokumentServiceImpl(DokumentRepository repository,
|
||||
ProductLookup productLookup,
|
||||
CustomerLookup customerLookup,
|
||||
PdfExportService pdfExportService) {
|
||||
ProductLookup productLookup,
|
||||
CustomerLookup customerLookup,
|
||||
PdfExportService pdfExportService) {
|
||||
this.repository = repository;
|
||||
this.productLookup = productLookup;
|
||||
this.customerLookup = customerLookup;
|
||||
|
|
@ -113,18 +113,23 @@ public class DokumentServiceImpl implements DokumentService {
|
|||
public Auftragsbestaetigung erstelleAuftragsbestaetigung(String angebotsNummer) {
|
||||
Angebot angebot = ladeAngebot(angebotsNummer);
|
||||
|
||||
// Status-Prüfung (PH-DP-04)
|
||||
if (angebot.getStatus() != DokumentStatus.OFFEN) {
|
||||
throw new IllegalStateException(
|
||||
"Angebot " + angebotsNummer + " ist nicht mehr OFFEN (Status: " +
|
||||
angebot.getStatus() + "). Auftragsbestätigung kann nicht erzeugt werden.");
|
||||
}
|
||||
|
||||
// Doppelte AB verhindern (PH-DP-05)
|
||||
// Doppelte AB zuerst (PH-DP-05 / MT-DP-06): wenn bereits eine AB für
|
||||
// dieses Angebot existiert, ist das Angebot zwangsläufig UEBERFUEHRT
|
||||
// und die Status-Prüfung würde ebenfalls anschlagen. Die Spec verlangt
|
||||
// in diesem Szenario eindeutig DuplicateDocumentException, deshalb
|
||||
// muss diese Prüfung vor dem allgemeinen Status-Check stehen.
|
||||
if (repository.existsFolgedokument(angebotsNummer)) {
|
||||
throw new DuplicateDocumentException(angebotsNummer);
|
||||
}
|
||||
|
||||
// Allgemeine Status-Prüfung (PH-DP-04): blockiert auch andere
|
||||
// Nicht-OFFEN-Zustände (z. B. ABGELEHNT, falls künftig ergänzt).
|
||||
if (angebot.getStatus() != DokumentStatus.OFFEN) {
|
||||
throw new IllegalStateException(
|
||||
"Angebot " + angebotsNummer + " ist nicht mehr OFFEN (Status: " +
|
||||
angebot.getStatus() + "). Auftragsbestätigung kann nicht erzeugt werden.");
|
||||
}
|
||||
|
||||
// AB erzeugen – Positionen 1:1 übernehmen
|
||||
Auftragsbestaetigung ab = new Auftragsbestaetigung();
|
||||
ab.setDokumentNummer(abGenerator.naechsteNummer());
|
||||
|
|
@ -156,8 +161,8 @@ public class DokumentServiceImpl implements DokumentService {
|
|||
|
||||
if (ab.getStatus() != DokumentStatus.BESTAETIGT) {
|
||||
throw new IllegalStateException(
|
||||
"Auftragsbestätigung " + auftragsNummer + " hat nicht den Status BESTAETIGT " +
|
||||
"(aktueller Status: " + ab.getStatus() + ").");
|
||||
"Auftragsbestätigung " + auftragsNummer + " hat nicht den Status BESTAETIGT " +
|
||||
"(aktueller Status: " + ab.getStatus() + ").");
|
||||
}
|
||||
|
||||
// Positionen ohne Preis-Snapshot (PH-DP-07)
|
||||
|
|
@ -189,8 +194,8 @@ public class DokumentServiceImpl implements DokumentService {
|
|||
|
||||
if (ls.getStatus() != DokumentStatus.GELIEFERT) {
|
||||
throw new IllegalStateException(
|
||||
"Lieferschein " + lieferscheinNummer + " hat nicht den Status GELIEFERT " +
|
||||
"(aktueller Status: " + ls.getStatus() + ").");
|
||||
"Lieferschein " + lieferscheinNummer + " hat nicht den Status GELIEFERT " +
|
||||
"(aktueller Status: " + ls.getStatus() + ").");
|
||||
}
|
||||
|
||||
// Kundendaten für Pflichtangaben holen
|
||||
|
|
@ -320,4 +325,4 @@ public class DokumentServiceImpl implements DokumentService {
|
|||
pa.setLeistungsdatum(lieferdatum);
|
||||
return pa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,33 +3,69 @@ package de.hsmannheim.faktura.gui;
|
|||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
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.Optional;
|
||||
|
||||
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.service.DokumentenService;
|
||||
import de.hsmannheim.faktura.dokumente.domain.DokumentType;
|
||||
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.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).
|
||||
* Zeigt Status und zulässige Folgeaktionen an. Deaktiviert unzulässige Aktionen (PHGUI10).
|
||||
* Festgeschriebene Rechnungen sind schreibgeschützt (PHGUI11).
|
||||
*
|
||||
* Verdrahtung gegen die spec-konforme Schnittstelle aus dem
|
||||
* 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 {
|
||||
|
||||
private final MainFrame mainFrame;
|
||||
private final DokumentenService dokumentenService;
|
||||
private final DokumentService dokumentService;
|
||||
private final TrackingDokumentRepository tracking;
|
||||
|
||||
// Tabelle
|
||||
private DefaultTableModel tableModel;
|
||||
private JTable dokumentTable;
|
||||
|
||||
// Detailfelder (schreibgeschützt)
|
||||
private JTextField txtDokumentnummer;
|
||||
private JTextField txtDokumentNummer;
|
||||
private JTextField txtKundenId;
|
||||
private JTextField txtDatum;
|
||||
private JTextField txtStatus;
|
||||
private JCheckBox chkFestgeschrieben;
|
||||
private JCheckBox chkFestgeschrieben;
|
||||
|
||||
// Positionstabelle
|
||||
private DefaultTableModel posTableModel;
|
||||
|
|
@ -42,8 +78,6 @@ public class DokumentPanel extends JPanel {
|
|||
|
||||
// Aktions-Buttons (PHGUI10)
|
||||
private JButton btnNeu;
|
||||
private JButton btnSpeichern;
|
||||
private JButton btnLoeschen;
|
||||
private JButton btnAbbrechen;
|
||||
private JButton btnPdfExport;
|
||||
|
||||
|
|
@ -56,19 +90,55 @@ public class DokumentPanel extends JPanel {
|
|||
|
||||
public DokumentPanel(MainFrame mainFrame) {
|
||||
this.mainFrame = mainFrame;
|
||||
this.dokumentenService = new DokumentenService(new DokumentRepository());
|
||||
|
||||
// DokumentServiceImpl braucht 4 Abhängigkeiten (DI laut Gruppe F):
|
||||
// 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));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
initComponents();
|
||||
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() {
|
||||
// --- Linke Seite: Dokumentenliste ---
|
||||
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
|
||||
leftPanel.setPreferredSize(new Dimension(300, 0));
|
||||
leftPanel.setPreferredSize(new Dimension(320, 0));
|
||||
|
||||
String[] cols = {"Nr.", "Typ ▲", "Status"};
|
||||
String[] cols = {"Nr.", "Typ", "Status"};
|
||||
tableModel = new DefaultTableModel(cols, 0) {
|
||||
public boolean isCellEditable(int r, int c) { return false; }
|
||||
};
|
||||
|
|
@ -76,14 +146,9 @@ public class DokumentPanel extends JPanel {
|
|||
// 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);
|
||||
}
|
||||
if (col == 2 && !isRowSelected(row)) {
|
||||
String status = String.valueOf(getValueAt(row, col));
|
||||
c.setForeground(farbeFuerStatus(status));
|
||||
} else {
|
||||
c.setForeground(Color.BLACK);
|
||||
}
|
||||
|
|
@ -91,292 +156,399 @@ public class DokumentPanel extends JPanel {
|
|||
}
|
||||
};
|
||||
dokumentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
// Sortierung (PHGUI08)
|
||||
dokumentTable.setAutoCreateRowSorter(true);
|
||||
dokumentTable.getTableHeader().setReorderingAllowed(false);
|
||||
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));
|
||||
// --- Rechte Seite: Detailbereich ---
|
||||
JPanel rightPanel = new JPanel(new BorderLayout(0, 8));
|
||||
rightPanel.add(kopfPanel(), BorderLayout.NORTH);
|
||||
rightPanel.add(positionsBereich(), BorderLayout.CENTER);
|
||||
rightPanel.add(buttonPanel(), BorderLayout.SOUTH);
|
||||
|
||||
// Kopfbereich
|
||||
JPanel headerPanel = new JPanel(new GridBagLayout());
|
||||
headerPanel.setBorder(BorderFactory.createTitledBorder("Dokumentdetail"));
|
||||
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
|
||||
split.setDividerLocation(320);
|
||||
add(split, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private JPanel kopfPanel() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
p.setBorder(BorderFactory.createTitledBorder("Dokumentkopf"));
|
||||
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.insets = new Insets(4, 6, 2, 6);
|
||||
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();
|
||||
g.fill = GridBagConstraints.HORIZONTAL;
|
||||
g.weightx = 1.0;
|
||||
|
||||
txtDokumentNummer = createReadOnlyField();
|
||||
txtKundenId = createReadOnlyField();
|
||||
g.gridx = 0; g.gridy = 2; headerPanel.add(txtDokumentnummer, g);
|
||||
g.gridx = 1; headerPanel.add(txtKundenId, g);
|
||||
txtDatum = createReadOnlyField();
|
||||
txtStatus = createReadOnlyField();
|
||||
chkFestgeschrieben = new JCheckBox("Festgeschrieben (PHGUI11)");
|
||||
chkFestgeschrieben.setEnabled(false);
|
||||
|
||||
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);
|
||||
int row = 0;
|
||||
row = addLabelField(p, g, "Dokumentnummer:", txtDokumentNummer, row);
|
||||
row = addLabelField(p, g, "Kunden-ID:", txtKundenId, row);
|
||||
row = addLabelField(p, g, "Datum:", txtDatum, row);
|
||||
row = addLabelField(p, g, "Status:", txtStatus, row);
|
||||
|
||||
rightPanel.add(headerPanel, BorderLayout.NORTH);
|
||||
g.gridx = 0; g.gridy = row; g.gridwidth = 2; p.add(chkFestgeschrieben, g);
|
||||
g.gridwidth = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Positionstabelle
|
||||
String[] posCols = {"Pos", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Summe"};
|
||||
private JPanel positionsBereich() {
|
||||
JPanel p = new JPanel(new BorderLayout(0, 4));
|
||||
p.setBorder(BorderFactory.createTitledBorder("Positionen / Summen"));
|
||||
|
||||
String[] posCols = {"Pos.", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Gesamt"};
|
||||
posTableModel = new DefaultTableModel(posCols, 0) {
|
||||
public boolean isCellEditable(int r, int c) { return false; }
|
||||
};
|
||||
posTable = new JTable(posTableModel);
|
||||
posTable.setEnabled(false);
|
||||
posTable.getTableHeader().setReorderingAllowed(false);
|
||||
p.add(new JScrollPane(posTable), BorderLayout.CENTER);
|
||||
|
||||
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);
|
||||
JPanel summenPanel = new JPanel(new GridLayout(1, 6, 8, 0));
|
||||
summenPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
summenPanel.add(new JLabel("Netto:"));
|
||||
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;
|
||||
}
|
||||
|
||||
// --- Daten ---
|
||||
private JPanel buttonPanel() {
|
||||
JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||
|
||||
btnNeu = new JButton("Auswahl zurücksetzen");
|
||||
btnAbbrechen = new JButton("Abbrechen");
|
||||
btnPdfExport = new JButton("PDF-Export");
|
||||
btnAngebotZuAuftrag = new JButton("→ Auftragsbestätigung");
|
||||
btnAuftragZuLieferschein = new JButton("→ Lieferschein");
|
||||
btnLieferscheinZuRechnung = new JButton("→ Rechnung");
|
||||
|
||||
btnNeu.addActionListener(e -> neuesFormular());
|
||||
btnAbbrechen.addActionListener(e -> neuesFormular());
|
||||
btnPdfExport.addActionListener(e -> pdfExport());
|
||||
btnAngebotZuAuftrag.addActionListener(e ->
|
||||
folgeaktion(DokumentType.ANGEBOT));
|
||||
btnAuftragZuLieferschein.addActionListener(e ->
|
||||
folgeaktion(DokumentType.AUFTRAGSBESTAETIGUNG));
|
||||
btnLieferscheinZuRechnung.addActionListener(e ->
|
||||
folgeaktion(DokumentType.LIEFERSCHEIN));
|
||||
|
||||
p.add(btnAngebotZuAuftrag);
|
||||
p.add(btnAuftragZuLieferschein);
|
||||
p.add(btnLieferscheinZuRechnung);
|
||||
p.add(btnPdfExport);
|
||||
p.add(btnNeu);
|
||||
p.add(btnAbbrechen);
|
||||
|
||||
// Initial alles deaktiviert (es ist nichts ausgewählt)
|
||||
setAktionenDeaktiviert();
|
||||
return p;
|
||||
}
|
||||
|
||||
private int addLabelField(JPanel p, GridBagConstraints g,
|
||||
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() {
|
||||
tableModel.setRowCount(0);
|
||||
List<Dokument> alle = dokumentenService.getAllDokumente();
|
||||
for (Dokument d : alle) {
|
||||
for (Dokument d : tracking.findAll()) {
|
||||
tableModel.addRow(new Object[]{
|
||||
d.getDokumentnummer(),
|
||||
d.getTypBezeichnung(),
|
||||
d.getStatus().getAnzeigename()
|
||||
d.getDokumentNummer(),
|
||||
typBezeichnung(d.getTyp()),
|
||||
d.getStatus().name()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void zeigeAusgewaehltes() {
|
||||
int row = dokumentTable.getSelectedRow();
|
||||
if (row < 0) return;
|
||||
if (row < 0) { aktuellesDokument = null; 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);
|
||||
});
|
||||
tracking.findByNummer(nr).ifPresent(this::zeigeDokument);
|
||||
}
|
||||
|
||||
private void zeigeDokument(Dokument d) {
|
||||
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;
|
||||
chkFestgeschrieben.setSelected(festgeschrieben);
|
||||
|
||||
// Positionen
|
||||
posTableModel.setRowCount(0);
|
||||
int pos = 1;
|
||||
boolean lieferschein = d.getTyp() == DokumentType.LIEFERSCHEIN; // PH-DP-07
|
||||
for (DokumentPosition p : d.getPositionen()) {
|
||||
posTableModel.addRow(new Object[]{
|
||||
pos++,
|
||||
p.getBezeichnungSnapshot(),
|
||||
p.getMenge(),
|
||||
lieferschein ? "—" : (p.getEinzelpreisNettoSnapshot() + " €"),
|
||||
lieferschein ? "—" : (formatProzent(p.getMwstSatzSnapshot())),
|
||||
lieferschein ? "—" : (p.getNettoGesamt() + " €")
|
||||
});
|
||||
}
|
||||
|
||||
// Summen (Lieferschein zeigt keine Preise – PH-DP-07)
|
||||
if (lieferschein) {
|
||||
lblNetto.setText("—");
|
||||
lblUst.setText("—");
|
||||
lblBrutto.setText("—");
|
||||
} else {
|
||||
lblNetto.setText(nullSichBd(d.getNettoSumme()) + " €");
|
||||
lblUst.setText( nullSichBd(d.getUstSumme()) + " €");
|
||||
lblBrutto.setText(nullSichBd(d.getBruttoSumme())+ " €");
|
||||
}
|
||||
|
||||
aktualisiereAktionen(d);
|
||||
}
|
||||
|
||||
private String nullSichBd(BigDecimal v) { return v == null ? "0.00" : v.toPlainString(); }
|
||||
|
||||
/**
|
||||
* Aktiviert nur die zulässigen Folgeaktionen basierend auf dem Dokumentstatus (PHGUI10).
|
||||
* Festgeschriebene Rechnungen: alle Bearbeitungsaktionen deaktiviert (PHGUI11).
|
||||
* Aktiviert nur die zulässigen Folgeaktionen basierend auf
|
||||
* Dokumenttyp und -status (PHGUI10).
|
||||
* Festgeschriebene Rechnungen: alle Aktionen außer PDF-Export
|
||||
* deaktiviert (PHGUI11).
|
||||
*/
|
||||
private void aktualisiereAktionen(Dokument d) {
|
||||
DokumentType typ = d.getTyp();
|
||||
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
|
||||
if (status == DokumentStatus.FESTGESCHRIEBEN) {
|
||||
btnAngebotZuAuftrag.setEnabled(false);
|
||||
btnAuftragZuLieferschein.setEnabled(false);
|
||||
btnLieferscheinZuRechnung.setEnabled(false);
|
||||
btnPdfExport.setEnabled(true); // PHGUI12
|
||||
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);
|
||||
btnPdfExport.setEnabled(true);
|
||||
btnAngebotZuAuftrag.setEnabled(
|
||||
typ == DokumentType.ANGEBOT && status == DokumentStatus.OFFEN);
|
||||
btnAuftragZuLieferschein.setEnabled(
|
||||
typ == DokumentType.AUFTRAGSBESTAETIGUNG && status == DokumentStatus.BESTAETIGT);
|
||||
btnLieferscheinZuRechnung.setEnabled(
|
||||
typ == DokumentType.LIEFERSCHEIN && status == DokumentStatus.GELIEFERT);
|
||||
}
|
||||
|
||||
private void setAktionenDeaktiviert() {
|
||||
btnSpeichern.setEnabled(false);
|
||||
btnLoeschen.setEnabled(false);
|
||||
btnPdfExport.setEnabled(false);
|
||||
btnAngebotZuAuftrag.setEnabled(false);
|
||||
btnAuftragZuLieferschein.setEnabled(false);
|
||||
btnLieferscheinZuRechnung.setEnabled(false);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Aktionen
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void neuesFormular() {
|
||||
aktuellesDokument = null;
|
||||
txtDokumentnummer.setText(""); txtKundenId.setText("");
|
||||
txtDatum.setText(""); txtStatus.setText("");
|
||||
dokumentTable.clearSelection();
|
||||
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 €");
|
||||
lblNetto.setText("0.00 €");
|
||||
lblUst.setText("0.00 €");
|
||||
lblBrutto.setText("0.00 €");
|
||||
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) {
|
||||
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.");
|
||||
if (aktuellesDokument.getTyp() != erwartet) {
|
||||
mainFrame.showStatus("Aktion passt nicht zum Dokumenttyp.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void folgeaktion(String typ) {
|
||||
if (aktuellesDokument == null) return;
|
||||
String quellNr = aktuellesDokument.getDokumentNummer();
|
||||
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());
|
||||
Dokument folge;
|
||||
switch (erwartet) {
|
||||
case ANGEBOT ->
|
||||
folge = dokumentService.erstelleAuftragsbestaetigung(quellNr); // PH-DP-04
|
||||
case AUFTRAGSBESTAETIGUNG ->
|
||||
folge = dokumentService.erstelleLieferschein(quellNr, LocalDate.now()); // PH-DP-06
|
||||
case LIEFERSCHEIN ->
|
||||
folge = dokumentService.erstelleRechnung(quellNr); // PH-DP-08
|
||||
default -> { mainFrame.showStatus("Kein Folgedokument vorgesehen."); return; }
|
||||
}
|
||||
ladeDokumente();
|
||||
mainFrame.showStatus("Folgedokument erfolgreich erstellt.");
|
||||
} catch (Exception ex) {
|
||||
mainFrame.showStatus("Folgedokument erstellt: " + folge.getDokumentNummer());
|
||||
} catch (RuntimeException ex) {
|
||||
// Fängt IllegalStateException, DuplicateDocumentException,
|
||||
// ValidationException, DokumentNotFoundException
|
||||
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) {
|
||||
Path pfad = dokumentService.exportiereAlsPdf(
|
||||
aktuellesDokument.getDokumentNummer()); // PHGUI12 / PH-DP-12
|
||||
mainFrame.showStatus("PDF erzeugt: " + pfad);
|
||||
} catch (RuntimeException ex) {
|
||||
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() {
|
||||
JTextField f = new JTextField();
|
||||
f.setEditable(false);
|
||||
f.setBackground(new Color(230, 230, 230));
|
||||
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); }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,29 +3,46 @@ package de.hsmannheim.faktura.gui;
|
|||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
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;
|
||||
import de.hsmannheim.faktura.kunde.model.Kunde;
|
||||
import de.hsmannheim.faktura.kunde.repository.KundeRepository;
|
||||
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.service.LoeschsperreException;
|
||||
import de.hsmannheim.faktura.kunde.service.ValidierungsException;
|
||||
|
||||
/**
|
||||
* Panel für die Kundenverwaltung (PHGUI02, PHGUI03, PHGUI05–PHGUI09).
|
||||
* Einheitliches Layout mit Navigationsbereich, Inhaltsbereich und Statusbereich (PHGUI02).
|
||||
*
|
||||
* Verdrahtung gegen die spec-konforme Schnittstelle K1 aus dem
|
||||
* gemeinsamen Pflichtenheft v1.1 (Kap. 6.1.2 / Kap. 7.2):
|
||||
* - Klassennamen: {@link KundeService}, {@link KundeRepository}, {@link Kunde}
|
||||
* - Methoden: anlegen / bearbeiten / suchen / loeschen / finde / alle
|
||||
* - Typ kundeId: long
|
||||
*
|
||||
* Das ursprüngliche GUI-Modell (KundenService / KundenRepository /
|
||||
* 8-arg-Konstruktor / getKundenId():int) ist eine Erfindung des
|
||||
* Gruppen-E-Pflichtenhefts und wird hier auf die gemeinsame Spec
|
||||
* gemappt. Eintrag im Fahrplan: OP-6 (GUI vs. PH v1.1).
|
||||
*/
|
||||
public class KundenPanel extends JPanel {
|
||||
|
||||
private final MainFrame mainFrame;
|
||||
private final KundenService kundenService;
|
||||
private final KundeService kundeService;
|
||||
|
||||
// Tabelle
|
||||
private DefaultTableModel tableModel;
|
||||
private JTable kundenTable;
|
||||
|
||||
// Formularfelder
|
||||
private JTextField txtKundenId;
|
||||
private JTextField txtKundeId;
|
||||
private JTextField txtFirmenname;
|
||||
private JTextField txtNachname;
|
||||
private JTextField txtVorname;
|
||||
private JTextField txtStrasse;
|
||||
private JTextField txtPlz;
|
||||
private JTextField txtOrt;
|
||||
|
|
@ -34,7 +51,7 @@ public class KundenPanel extends JPanel {
|
|||
private JTextField txtUstIdNr;
|
||||
|
||||
// Fehlerhinweise (PHGUI03, PHGUI05)
|
||||
private JLabel lblFirmenfehler;
|
||||
private JLabel lblNameFehler;
|
||||
private JLabel lblStrasseFehler;
|
||||
private JLabel lblPlzFehler;
|
||||
private JLabel lblOrtFehler;
|
||||
|
|
@ -51,9 +68,10 @@ public class KundenPanel extends JPanel {
|
|||
|
||||
public KundenPanel(MainFrame mainFrame) {
|
||||
this.mainFrame = mainFrame;
|
||||
this.kundenService = new KundenService(
|
||||
new KundenRepository(),
|
||||
createDummyDokumentRepository()
|
||||
this.kundeService = new KundeService(
|
||||
new KundeRepositoryJson(new File("data/kunden.json")),
|
||||
new KundeValidator(),
|
||||
stubBelegPruefer()
|
||||
);
|
||||
setLayout(new BorderLayout(8, 8));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
|
@ -61,153 +79,183 @@ public class KundenPanel extends JPanel {
|
|||
ladeKunden();
|
||||
}
|
||||
|
||||
/**
|
||||
* GR-05-Löschsperre lokal deaktiviert: das DokumentPanel hat eine
|
||||
* eigene Repository-Instanz, eine geteilte Belegsicht erfordert ein
|
||||
* zentrales Wiring (über MainFrame) und ist Bestandteil von OP-6.
|
||||
*/
|
||||
private BelegReferenzPruefer stubBelegPruefer() {
|
||||
return kundeId -> false;
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
// --- Linke Seite: Liste + Suche ---
|
||||
// --- Linke Seite: Liste + Suche (PHGUI09) ---
|
||||
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
|
||||
leftPanel.setPreferredSize(new Dimension(320, 0));
|
||||
|
||||
String[] columns = {"ID", "Name ▲", "Ort"};
|
||||
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.getTableHeader().setReorderingAllowed(false);
|
||||
kundenTable.setAutoCreateRowSorter(true); // PHGUI08
|
||||
|
||||
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)"));
|
||||
JPanel suchPanel = new JPanel(new BorderLayout(4, 0));
|
||||
suchPanel.add(new JLabel("Suche: "), BorderLayout.WEST);
|
||||
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 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);
|
||||
suchPanel.add(txtSuche, BorderLayout.CENTER);
|
||||
|
||||
// --- Rechte Seite: Formular ---
|
||||
JPanel rightPanel = new JPanel(new BorderLayout(4, 8));
|
||||
rightPanel.setBorder(BorderFactory.createTitledBorder("Kundendetails Pflichtfelder sind mit * markiert"));
|
||||
leftPanel.add(suchPanel, BorderLayout.NORTH);
|
||||
leftPanel.add(new JScrollPane(kundenTable), BorderLayout.CENTER);
|
||||
|
||||
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(); });
|
||||
// --- Rechte Seite: Formular + Buttons (PHGUI02) ---
|
||||
JPanel rightPanel = new JPanel(new BorderLayout(0, 8));
|
||||
rightPanel.add(formularPanel(), BorderLayout.CENTER);
|
||||
rightPanel.add(buttonPanel(), BorderLayout.SOUTH);
|
||||
|
||||
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 JPanel formularPanel() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
p.setBorder(BorderFactory.createTitledBorder("Kundendaten"));
|
||||
GridBagConstraints g = new GridBagConstraints();
|
||||
g.insets = new Insets(4, 6, 2, 6);
|
||||
g.anchor = GridBagConstraints.WEST;
|
||||
g.fill = GridBagConstraints.HORIZONTAL;
|
||||
g.weightx = 1.0;
|
||||
|
||||
txtKundeId = createReadOnlyField();
|
||||
txtFirmenname = new JTextField();
|
||||
txtNachname = new JTextField();
|
||||
txtVorname = new JTextField();
|
||||
txtStrasse = new JTextField();
|
||||
txtPlz = new JTextField();
|
||||
txtOrt = new JTextField();
|
||||
txtTelefon = new JTextField();
|
||||
txtEmail = new JTextField();
|
||||
txtUstIdNr = new JTextField();
|
||||
|
||||
lblNameFehler = createFehlerLabel();
|
||||
lblStrasseFehler = createFehlerLabel();
|
||||
lblPlzFehler = createFehlerLabel();
|
||||
lblOrtFehler = createFehlerLabel();
|
||||
lblEmailFehler = createFehlerLabel();
|
||||
lblEmailFehler.setText("ⓘ Format: name@domain.tld");
|
||||
lblEmailFehler.setForeground(new Color(120, 120, 120));
|
||||
|
||||
int row = 0;
|
||||
row = addLabelField(p, g, "Kunden-ID:", txtKundeId, null, row);
|
||||
row = addLabelField(p, g, "Firmenname *:", txtFirmenname, lblNameFehler, row);
|
||||
row = addLabelField(p, g, "Nachname *:", txtNachname, null, row);
|
||||
row = addLabelField(p, g, "Vorname:", txtVorname, null, row);
|
||||
row = addLabelField(p, g, "Straße + Nr. *:", txtStrasse, lblStrasseFehler, row);
|
||||
row = addLabelField(p, g, "PLZ *:", txtPlz, lblPlzFehler, row);
|
||||
row = addLabelField(p, g, "Ort *:", txtOrt, lblOrtFehler, row);
|
||||
row = addLabelField(p, g, "Telefon:", txtTelefon, null, row);
|
||||
row = addLabelField(p, g, "E-Mail:", txtEmail, lblEmailFehler, row);
|
||||
row = addLabelField(p, g, "USt-IdNr.:", txtUstIdNr, null, row);
|
||||
|
||||
// Fülldummy, damit obere Felder oben bleiben
|
||||
g.gridx = 0; g.gridy = row; g.weighty = 1.0;
|
||||
p.add(new JLabel(" "), g);
|
||||
|
||||
JLabel hint = new JLabel("* Pflichtfeld – Firmenname ODER Nachname ist Pflicht (PH v1.1 Kap. 6.1.2)");
|
||||
hint.setFont(new Font("SansSerif", Font.ITALIC, 11));
|
||||
hint.setForeground(new Color(120, 120, 120));
|
||||
|
||||
JPanel wrap = new JPanel(new BorderLayout());
|
||||
wrap.add(p, BorderLayout.CENTER);
|
||||
wrap.add(hint, BorderLayout.SOUTH);
|
||||
return wrap;
|
||||
}
|
||||
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);
|
||||
|
||||
private int addLabelField(JPanel p, GridBagConstraints g,
|
||||
String label, JComponent field, JLabel fehlerLabel, 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);
|
||||
row++;
|
||||
if (fehlerLabel != null) {
|
||||
g.gridx = 1; g.gridy = row; g.weightx = 1.0; p.add(fehlerLabel, g);
|
||||
row++;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private JPanel buttonPanel() {
|
||||
JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||
btnNeu = new JButton("Neu");
|
||||
btnSpeichern = new JButton("Speichern");
|
||||
btnLoeschen = new JButton("Löschen");
|
||||
btnAbbrechen = new JButton("Abbrechen");
|
||||
|
||||
btnNeu.addActionListener(e -> neuesFormular());
|
||||
btnSpeichern.addActionListener(e -> speichern());
|
||||
btnLoeschen.addActionListener(e -> loeschen());
|
||||
btnAbbrechen.addActionListener(e -> { neuesFormular(); kundenTable.clearSelection(); });
|
||||
|
||||
p.add(btnNeu); p.add(btnSpeichern); p.add(btnLoeschen); p.add(btnAbbrechen);
|
||||
return p;
|
||||
}
|
||||
|
||||
// --- Daten ---
|
||||
|
||||
private void ladeKunden() {
|
||||
ladeKundenListe(kundenService.getAllKunden());
|
||||
ladeKundenListe(kundeService.alle());
|
||||
}
|
||||
|
||||
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()
|
||||
Long.toString(k.getKundeId()),
|
||||
nameVon(k),
|
||||
nullAls(k.getOrt())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anzeigename gemäß PH v1.1 Kap. 6.1.2: firmenname ODER nachname ist
|
||||
* Pflicht. Liefert firmenname, sonst „nachname[, vorname]“.
|
||||
*/
|
||||
private static String nameVon(Kunde k) {
|
||||
if (k.getFirmenname() != null && !k.getFirmenname().isBlank()) {
|
||||
return k.getFirmenname();
|
||||
}
|
||||
String n = nullAls(k.getNachname());
|
||||
if (k.getVorname() != null && !k.getVorname().isBlank()) {
|
||||
return n + ", " + k.getVorname();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private static String nullAls(String s) { return s == null ? "" : s; }
|
||||
|
||||
private void sucheAktualisieren() {
|
||||
// PHGUI09: Groß-/Kleinschreibung ignorieren, nach Name oder ID suchen
|
||||
// PHGUI09: Treffer über Name UND Kundennummer.
|
||||
// KundeService.suchen("") liefert bewusst eine leere Liste (P4-3),
|
||||
// deshalb hier explizit auf .alle() umschalten.
|
||||
String query = txtSuche.getText();
|
||||
ladeKundenListe(kundenService.searchKunden(query));
|
||||
if (query == null || query.isBlank()) {
|
||||
ladeKundenListe(kundeService.alle());
|
||||
} else {
|
||||
ladeKundenListe(kundeService.suchen(query));
|
||||
}
|
||||
}
|
||||
|
||||
private void zeigeAusgewaehlten() {
|
||||
|
|
@ -215,118 +263,144 @@ public class KundenPanel extends JPanel {
|
|||
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();
|
||||
});
|
||||
try {
|
||||
long kundeId = Long.parseLong(idStr);
|
||||
kundeService.finde(kundeId).ifPresent(this::fuelleFormular);
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Tabelle in inkonsistentem Zustand; einfach ignorieren
|
||||
}
|
||||
}
|
||||
|
||||
private void fuelleFormular(Kunde k) {
|
||||
txtKundeId.setText(Long.toString(k.getKundeId()));
|
||||
txtFirmenname.setText(nullAls(k.getFirmenname()));
|
||||
txtNachname.setText(nullAls(k.getNachname()));
|
||||
txtVorname.setText(nullAls(k.getVorname()));
|
||||
txtStrasse.setText(nullAls(k.getStrasse()));
|
||||
txtPlz.setText(nullAls(k.getPlz()));
|
||||
txtOrt.setText(nullAls(k.getOrt()));
|
||||
txtTelefon.setText(nullAls(k.getTelefon()));
|
||||
txtEmail.setText(nullAls(k.getEmail()));
|
||||
txtUstIdNr.setText(nullAls(k.getUstIdNr()));
|
||||
clearFehler();
|
||||
}
|
||||
|
||||
// --- Aktionen ---
|
||||
|
||||
private void neuesFormular() {
|
||||
txtKundenId.setText(""); txtFirmenname.setText(""); txtStrasse.setText("");
|
||||
txtPlz.setText(""); txtOrt.setText(""); txtTelefon.setText("");
|
||||
txtEmail.setText(""); txtUstIdNr.setText("");
|
||||
txtKundeId.setText("");
|
||||
txtFirmenname.setText(""); txtNachname.setText(""); txtVorname.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;
|
||||
// GUI-seitige Vorprüfung der Pflichtfelder (PHGUI03) - die fachliche
|
||||
// Validierung gegen PH v1.1 macht der KundeValidator beim Service-Aufruf.
|
||||
boolean clientValid = true;
|
||||
if (istLeer(txtFirmenname) && istLeer(txtNachname)) {
|
||||
lblNameFehler.setText("⚠ Firmenname ODER Nachname erforderlich");
|
||||
clientValid = false;
|
||||
}
|
||||
if (istLeer(txtStrasse)) { lblStrasseFehler.setText("⚠ Pflichtfeld"); clientValid = false; }
|
||||
if (istLeer(txtPlz)) { lblPlzFehler.setText("⚠ Pflichtfeld"); clientValid = false; }
|
||||
if (istLeer(txtOrt)) { lblOrtFehler.setText("⚠ Pflichtfeld"); clientValid = false; }
|
||||
if (!clientValid) return;
|
||||
|
||||
// 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;
|
||||
// Kunde-Objekt aufbauen (no-arg-Konstruktor + Setter; Jackson-kompatibel)
|
||||
Kunde k = new Kunde();
|
||||
if (!txtKundeId.getText().isBlank()) {
|
||||
try { k.setKundeId(Long.parseLong(txtKundeId.getText().trim())); }
|
||||
catch (NumberFormatException ignored) { /* bleibt 0 = Neuanlage */ }
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
k.setFirmenname(leerAlsNull(txtFirmenname));
|
||||
k.setNachname(leerAlsNull(txtNachname));
|
||||
k.setVorname(leerAlsNull(txtVorname));
|
||||
k.setStrasse(leerAlsNull(txtStrasse));
|
||||
k.setPlz(leerAlsNull(txtPlz));
|
||||
k.setOrt(leerAlsNull(txtOrt));
|
||||
k.setTelefon(leerAlsNull(txtTelefon));
|
||||
k.setEmail(leerAlsNull(txtEmail));
|
||||
k.setUstIdNr(leerAlsNull(txtUstIdNr));
|
||||
|
||||
try {
|
||||
kundenService.saveKunde(k);
|
||||
if (k.getKundeId() == 0) {
|
||||
long neueId = kundeService.anlegen(k); // F-KV-01
|
||||
k.setKundeId(neueId);
|
||||
} else {
|
||||
kundeService.bearbeiten(k); // F-KV-02
|
||||
}
|
||||
ladeKunden();
|
||||
mainFrame.showStatus("Kunde gespeichert (ID " + k.getKundeId() + ")."); // PHGUI06
|
||||
neuesFormular();
|
||||
// PHGUI06: Bestätigung < 1 Sekunde
|
||||
mainFrame.showStatus("Kunde erfolgreich gespeichert.");
|
||||
} catch (IllegalArgumentException ex) {
|
||||
mainFrame.showStatus("Fehler: " + ex.getMessage());
|
||||
} catch (ValidierungsException ex) {
|
||||
// Fehler aus KundeValidator (PH v1.1 Kap. 6.1.2) sichtbar machen
|
||||
String msg = ex.getMessage();
|
||||
if (msg.toLowerCase().contains("email") || msg.toLowerCase().contains("e-mail")) {
|
||||
lblEmailFehler.setText("⚠ " + msg);
|
||||
lblEmailFehler.setForeground(new Color(192, 57, 43));
|
||||
} else if (msg.toLowerCase().contains("firmen") || msg.toLowerCase().contains("nach")) {
|
||||
lblNameFehler.setText("⚠ " + msg);
|
||||
} else if (msg.toLowerCase().contains("plz")) {
|
||||
lblPlzFehler.setText("⚠ " + msg);
|
||||
} else if (msg.toLowerCase().contains("straße") || msg.toLowerCase().contains("strasse")) {
|
||||
lblStrasseFehler.setText("⚠ " + msg);
|
||||
} else if (msg.toLowerCase().contains("ort")) {
|
||||
lblOrtFehler.setText("⚠ " + msg);
|
||||
} else {
|
||||
mainFrame.showStatus("Validierung: " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loeschen() {
|
||||
int row = kundenTable.getSelectedRow();
|
||||
if (row < 0) {
|
||||
mainFrame.showStatus("Kein Kunde ausgewählt.");
|
||||
return;
|
||||
}
|
||||
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
|
||||
);
|
||||
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) {
|
||||
try {
|
||||
long kundeId = Long.parseLong(idStr);
|
||||
kundeService.loeschen(kundeId); // F-KV-04
|
||||
ladeKunden();
|
||||
neuesFormular();
|
||||
mainFrame.showStatus("Kunde gelöscht.");
|
||||
} else {
|
||||
mainFrame.showStatus("Löschen nicht möglich – Kunde wird in Dokumenten verwendet.");
|
||||
} catch (LoeschsperreException ex) {
|
||||
mainFrame.showStatus("Löschen nicht möglich – " + ex.getMessage()); // GR-05
|
||||
} catch (ValidierungsException ex) {
|
||||
mainFrame.showStatus("Fehler: " + ex.getMessage());
|
||||
} catch (NumberFormatException ex) {
|
||||
mainFrame.showStatus("Ungültige Kunden-ID.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hilfsmethoden ---
|
||||
|
||||
private boolean istLeer(JTextField f) {
|
||||
return f.getText() == null || f.getText().trim().isEmpty();
|
||||
}
|
||||
|
||||
private String leerAlsNull(JTextField f) {
|
||||
String s = f.getText() == null ? null : f.getText().trim();
|
||||
return (s == null || s.isEmpty()) ? null : s;
|
||||
}
|
||||
|
||||
private void clearFehler() {
|
||||
lblFirmenfehler.setText("");
|
||||
lblNameFehler.setText("");
|
||||
lblStrasseFehler.setText("");
|
||||
lblPlzFehler.setText("");
|
||||
lblOrtFehler.setText("");
|
||||
lblEmailFehler.setText("▲ Format: name@domain.tld");
|
||||
lblEmailFehler.setText("ⓘ Format: name@domain.tld");
|
||||
lblEmailFehler.setForeground(new Color(120, 120, 120));
|
||||
}
|
||||
|
||||
|
|
@ -337,15 +411,10 @@ public class KundenPanel extends JPanel {
|
|||
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; }
|
||||
};
|
||||
private JTextField createReadOnlyField() {
|
||||
JTextField f = new JTextField();
|
||||
f.setEditable(false);
|
||||
f.setBackground(new Color(230, 230, 230));
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue