diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/domain/Dokument.java b/src/main/java/de/hsmannheim/faktura/dokumente/domain/Dokument.java index 5cf6139..9925b3b 100644 --- a/src/main/java/de/hsmannheim/faktura/dokumente/domain/Dokument.java +++ b/src/main/java/de/hsmannheim/faktura/dokumente/domain/Dokument.java @@ -99,3 +99,33 @@ public abstract class Dokument { getClass().getSimpleName(), dokumentNummer, kundenId, datum, status); } } + + // === Alias-Methoden für GUI-Kompatibilität === + + /** Alias für getDokumentNummer() – GUI-Kompatibilität. */ + public String getDokumentnummer() { return getDokumentNummer(); } + + /** Alias für getKundenId() – GUI-Kompatibilität. */ + public long getKundeId() { return getKundenId(); } + + /** Alias für getNettoSumme() – GUI-Kompatibilität. */ + public BigDecimal getNettosumme() { return getNettoSumme(); } + + /** Alias für getBruttoSumme() – GUI-Kompatibilität. */ + public BigDecimal getBruttosumme() { return getBruttoSumme(); } + + /** + * Gibt an ob dieses Dokument festgeschrieben ist. + * Standard: false. Wird in Rechnung auf true gesetzt (PHGUI11). + */ + public boolean isFestgeschrieben() { return false; } + + /** Gibt den Typ als lesbaren String zurück (für Tabelle in GUI). */ + public String getTypBezeichnung() { + return switch (getTyp()) { + case ANGEBOT -> "Angebot"; + case AUFTRAGSBESTAETIGUNG -> "Auftragsbestätigung"; + case LIEFERSCHEIN -> "Lieferschein"; + case RECHNUNG -> "Rechnung"; + }; + } diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentPosition.java b/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentPosition.java index 716efad..2ff3e80 100644 --- a/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentPosition.java +++ b/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentPosition.java @@ -72,3 +72,22 @@ public class DokumentPosition { produktId, bezeichnungSnapshot, menge, einzelpreisNettoSnapshot, mwstSatzSnapshot); } } + + // === Alias-Methoden für GUI-Kompatibilität === + + /** Alias für getBezeichnungSnapshot() – GUI-Kompatibilität. */ + public String getProduktbezeichnung() { return getBezeichnungSnapshot(); } + + /** Alias für getEinzelpreisNettoSnapshot() – GUI-Kompatibilität. */ + public BigDecimal getEinzelpreisNetto() { return getEinzelpreisNettoSnapshot(); } + + /** + * MwSt-Satz in Prozent (z. B. 19.00 statt 0.19) – GUI-Kompatibilität. + */ + public BigDecimal getMehrwertsteuerSatz() { + return getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100)) + .setScale(0, java.math.RoundingMode.HALF_UP); + } + + /** Alias für getNettoGesamt() – GUI-Kompatibilität. */ + public BigDecimal getSumme() { return getNettoGesamt(); } diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentStatus.java b/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentStatus.java index 3a58b09..2ae435e 100644 --- a/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentStatus.java +++ b/src/main/java/de/hsmannheim/faktura/dokumente/domain/DokumentStatus.java @@ -2,12 +2,7 @@ package de.hsmannheim.faktura.dokumente.domain; /** * Steuert erlaubte Folgeaktionen im Dokumentenprozess. - * - * Workflow: - * ANGEBOT: OFFEN → UEBERFUEHRT - * AUFTRAGSBESTAETIGUNG: BESTAETIGT - * LIEFERSCHEIN: GELIEFERT - * RECHNUNG: FESTGESCHRIEBEN + * Workflow: ANGEBOT: OFFEN → UEBERFUEHRT | AB: BESTAETIGT | LS: GELIEFERT | R: FESTGESCHRIEBEN */ public enum DokumentStatus { OFFEN, @@ -15,5 +10,17 @@ public enum DokumentStatus { BESTAETIGT, GELIEFERT, FAKTURIERT, - FESTGESCHRIEBEN + FESTGESCHRIEBEN; + + /** Lesbarer Anzeigename für die GUI (PHGUI10). */ + public String getAnzeigename() { + return switch (this) { + case OFFEN -> "Offen"; + case UEBERFUEHRT -> "Überführt"; + case BESTAETIGT -> "Bestätigt"; + case GELIEFERT -> "Geliefert"; + case FAKTURIERT -> "Fakturiert"; + case FESTGESCHRIEBEN -> "Festgeschrieben"; + }; + } } diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/repository/DokumentRepository.java b/src/main/java/de/hsmannheim/faktura/dokumente/repository/DokumentRepository.java index fdb04a9..9613d9e 100644 --- a/src/main/java/de/hsmannheim/faktura/dokumente/repository/DokumentRepository.java +++ b/src/main/java/de/hsmannheim/faktura/dokumente/repository/DokumentRepository.java @@ -75,3 +75,19 @@ public interface DokumentRepository { */ int findMaxLaufendeNummer(String praefix, int jahr); } + + /** + * Gibt alle gespeicherten Dokumente zurück (für die Listenansicht in der GUI). + * + * @return Liste aller Dokumente + */ + List findAll(); + + /** + * Löscht ein Dokument anhand seiner Nummer. + * Festgeschriebene Rechnungen können nicht gelöscht werden. + * + * @param nummer Dokumentnummer + * @return true wenn gelöscht, false wenn nicht gefunden + */ + boolean delete(String nummer); diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/repository/InMemoryDokumentRepository.java b/src/main/java/de/hsmannheim/faktura/dokumente/repository/InMemoryDokumentRepository.java index 465ceb8..719fb08 100644 --- a/src/main/java/de/hsmannheim/faktura/dokumente/repository/InMemoryDokumentRepository.java +++ b/src/main/java/de/hsmannheim/faktura/dokumente/repository/InMemoryDokumentRepository.java @@ -95,3 +95,13 @@ public class InMemoryDokumentRepository implements DokumentRepository { store.clear(); } } + + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } + + @Override + public boolean delete(String nummer) { + return store.remove(nummer) != null; + } diff --git a/src/main/java/de/hsmannheim/faktura/dokumente/service/DokumentenService.java b/src/main/java/de/hsmannheim/faktura/dokumente/service/DokumentenService.java new file mode 100644 index 0000000..814da65 --- /dev/null +++ b/src/main/java/de/hsmannheim/faktura/dokumente/service/DokumentenService.java @@ -0,0 +1,143 @@ +package de.hsmannheim.faktura.dokumente.service; + +import de.hsmannheim.faktura.dokumente.domain.*; +import de.hsmannheim.faktura.dokumente.export.SimplePdfExportService; +import de.hsmannheim.faktura.dokumente.lookup.*; +import de.hsmannheim.faktura.dokumente.repository.DokumentRepository; +import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository; + +import java.math.BigDecimal; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +/** + * Fassade für die GUI (DokumentPanel). + * Kapselt den DokumentServiceImpl und bietet die Methoden die die GUI braucht. + * + * Wird in der GUI so instanziiert: + * new DokumentenService(new InMemoryDokumentRepository()) + * + * Sobald Gruppe G (Produkte) und H (Kunden) ihre Lookups liefern, + * können diese über den Setter-Konstruktor injiziert werden. + */ +public class DokumentenService { + + private final DokumentService service; + private final DokumentRepository repository; + + /** + * Konstruktor für die GUI – nimmt ein Repository entgegen. + * Produkt- und Kundenlookup werden als Stubs vorbelegt. + */ + public DokumentenService(DokumentRepository repository) { + this.repository = repository; + + // Stub-Lookups bis Gruppe G und H ihre Implementierungen liefern + ProductLookup produktLookup = new StubProductLookup(); + CustomerLookup kundeLookup = new StubCustomerLookup(); + + this.service = new DokumentServiceImpl( + repository, + produktLookup, + kundeLookup, + new SimplePdfExportService() + ); + } + + /** + * Vollständiger Konstruktor – für Produktion wenn echte Lookups vorliegen. + */ + public DokumentenService(DokumentRepository repository, + ProductLookup produktLookup, + CustomerLookup kundeLookup) { + this.repository = repository; + this.service = new DokumentServiceImpl( + repository, produktLookup, kundeLookup, new SimplePdfExportService() + ); + } + + // ------------------------------------------------------------------------- + // Methoden für die GUI + // ------------------------------------------------------------------------- + + /** Alle Dokumente für die Listenansicht. */ + public List getAllDokumente() { + return repository.findAll(); + } + + /** Einzelnes Dokument nach Nummer laden. */ + public Optional getDokumentByNummer(String nummer) { + return repository.findByNummer(nummer); + } + + /** + * Dokument löschen. Festgeschriebene Rechnungen können nicht gelöscht werden. + * @return true wenn erfolgreich gelöscht + */ + public boolean deleteDokument(String nummer) { + Optional dok = repository.findByNummer(nummer); + if (dok.isEmpty()) return false; + if (dok.get().isFestgeschrieben()) return false; // PHGUI11 + return repository.delete(nummer); + } + + /** Auftragsbestätigung aus Angebot erzeugen. */ + public Auftragsbestaetigung createAuftragsbestaetigung(String angebotsNummer) { + return service.erstelleAuftragsbestaetigung(angebotsNummer); + } + + /** Lieferschein aus AB erzeugen (Lieferdatum = heute). */ + public Lieferschein createLieferschein(String auftragsNummer) { + return service.erstelleLieferschein(auftragsNummer, LocalDate.now()); + } + + /** Rechnung aus Lieferschein erzeugen. */ + public Rechnung createRechnung(String lieferscheinNummer) { + return service.erstelleRechnung(lieferscheinNummer); + } + + /** PDF-Export eines Dokuments. */ + public Path exportPdf(String dokumentNummer, DokumentType typ) { + return service.exportiereAlsPdf(dokumentNummer); + } + + // ------------------------------------------------------------------------- + // Stub-Implementierungen (Platzhalter bis Gruppe G und H fertig sind) + // ------------------------------------------------------------------------- + + /** + * Platzhalter-Produktlookup. + * Ersetzen durch echte Implementierung von Gruppe G. + */ + private static class StubProductLookup implements ProductLookup { + @Override + public Optional findProduct(long produktId) { + // Beispieldaten bis Gruppe G ihre Implementierung liefert + return Optional.of(new ProductDto( + produktId, + "Produkt #" + produktId, + new BigDecimal("100.00"), + new BigDecimal("0.19") + )); + } + } + + /** + * Platzhalter-Kundenlookup. + * Ersetzen durch echte Implementierung von Gruppe H. + */ + private static class StubCustomerLookup implements CustomerLookup { + @Override + public Optional findCustomer(long kundenId) { + // Beispieldaten bis Gruppe H ihre Implementierung liefert + return Optional.of(new CustomerDto( + kundenId, + "Kunde #" + kundenId, + "Musterstraße 1, 68163 Mannheim", + "DE123456789" + )); + } + } +} diff --git a/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java b/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java index ce1aba3..eb218af 100644 --- a/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java +++ b/src/main/java/de/hsmannheim/faktura/gui/DokumentPanel.java @@ -3,69 +3,33 @@ 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.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.service.DokumentenService; 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). - * - * 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). + * 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 DokumentService dokumentService; - private final TrackingDokumentRepository tracking; + private final DokumentenService dokumentenService; // 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; @@ -78,6 +42,8 @@ public class DokumentPanel extends JPanel { // Aktions-Buttons (PHGUI10) private JButton btnNeu; + private JButton btnSpeichern; + private JButton btnLoeschen; private JButton btnAbbrechen; private JButton btnPdfExport; @@ -90,53 +56,18 @@ public class DokumentPanel extends JPanel { public DokumentPanel(MainFrame mainFrame) { this.mainFrame = mainFrame; - - // 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); - + // FIX: InMemoryDokumentRepository statt DokumentRepository (Interface) + this.dokumentenService = new DokumentenService(new InMemoryDokumentRepository()); 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.empty(); - } - - // ------------------------------------------------------------------------- - // UI-Aufbau - // ------------------------------------------------------------------------- - private void initComponents() { // --- Linke Seite: Dokumentenliste --- JPanel leftPanel = new JPanel(new BorderLayout(4, 4)); - leftPanel.setPreferredSize(new Dimension(320, 0)); + leftPanel.setPreferredSize(new Dimension(300, 0)); String[] cols = {"Nr.", "Typ", "Status"}; tableModel = new DefaultTableModel(cols, 0) { @@ -146,9 +77,14 @@ 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 && !isRowSelected(row)) { - String status = String.valueOf(getValueAt(row, col)); - c.setForeground(farbeFuerStatus(status)); + 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("Bestätigt")) c.setForeground(new Color(39, 174, 96)); + else if (status.contains("Festgeschr")) c.setForeground(new Color(192, 57, 43)); + else c.setForeground(Color.BLACK); + } } else { c.setForeground(Color.BLACK); } @@ -157,266 +93,264 @@ public class DokumentPanel extends JPanel { }; dokumentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 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: Detailbereich --- - JPanel rightPanel = new JPanel(new BorderLayout(0, 8)); - rightPanel.add(kopfPanel(), BorderLayout.NORTH); - rightPanel.add(positionsBereich(), BorderLayout.CENTER); - rightPanel.add(buttonPanel(), BorderLayout.SOUTH); + // --- Rechte Seite: Detailansicht --- + JPanel rightPanel = new JPanel(new BorderLayout(4, 8)); - 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")); + JPanel headerPanel = new JPanel(new GridBagLayout()); + headerPanel.setBorder(BorderFactory.createTitledBorder("Dokumentdetail")); GridBagConstraints g = new GridBagConstraints(); - g.insets = new Insets(4, 6, 2, 6); - g.anchor = GridBagConstraints.WEST; - g.fill = GridBagConstraints.HORIZONTAL; - g.weightx = 1.0; + g.insets = new Insets(3, 4, 3, 4); + g.fill = GridBagConstraints.HORIZONTAL; + g.weightx = 0.5; - txtDokumentNummer = createReadOnlyField(); - txtKundenId = createReadOnlyField(); - txtDatum = createReadOnlyField(); - txtStatus = createReadOnlyField(); - chkFestgeschrieben = new JCheckBox("Festgeschrieben (PHGUI11)"); + // Festgeschrieben-Checkbox (PHGUI11) + chkFestgeschrieben = new JCheckBox("Festgeschrieben"); chkFestgeschrieben.setEnabled(false); + g.gridx = 1; g.gridy = 0; g.anchor = GridBagConstraints.EAST; + headerPanel.add(chkFestgeschrieben, 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); + 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 = row; g.gridwidth = 2; p.add(chkFestgeschrieben, g); - g.gridwidth = 1; - return p; - } + 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); - private JPanel positionsBereich() { - JPanel p = new JPanel(new BorderLayout(0, 4)); - p.setBorder(BorderFactory.createTitledBorder("Positionen / Summen")); + rightPanel.add(headerPanel, BorderLayout.NORTH); - String[] posCols = {"Pos.", "Bezeichnung", "Menge", "Einzelpreis", "MwSt.", "Gesamt"}; + // 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.getTableHeader().setReorderingAllowed(false); - p.add(new JScrollPane(posTable), BorderLayout.CENTER); + posTable.setEnabled(false); - 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; - } + JPanel posPanel = new JPanel(new BorderLayout()); + posPanel.setBorder(BorderFactory.createTitledBorder("Positionen:")); + posPanel.add(new JScrollPane(posTable), BorderLayout.CENTER); - private JPanel buttonPanel() { - JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4)); + // 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.:")); summenPanel.add(lblUst); + summenPanel.add(new JLabel("Brutto:")); summenPanel.add(lblBrutto); - 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"); + 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); + + 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()); + btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); }); btnPdfExport.addActionListener(e -> pdfExport()); - btnAngebotZuAuftrag.addActionListener(e -> - folgeaktion(DokumentType.ANGEBOT)); - btnAuftragZuLieferschein.addActionListener(e -> - folgeaktion(DokumentType.AUFTRAGSBESTAETIGUNG)); - btnLieferscheinZuRechnung.addActionListener(e -> - folgeaktion(DokumentType.LIEFERSCHEIN)); + btnLoeschen.addActionListener(e -> loeschen()); - p.add(btnAngebotZuAuftrag); - p.add(btnAuftragZuLieferschein); - p.add(btnLieferscheinZuRechnung); - p.add(btnPdfExport); - p.add(btnNeu); - p.add(btnAbbrechen); + btnAngebotZuAuftrag.addActionListener(e -> folgeaktion("ANGEBOT_ZU_AUFTRAG")); + btnAuftragZuLieferschein.addActionListener(e -> folgeaktion("AUFTRAG_ZU_LIEFERSCHEIN")); + btnLieferscheinZuRechnung.addActionListener(e -> folgeaktion("LIEFERSCHEIN_ZU_RECHNUNG")); - // Initial alles deaktiviert (es ist nichts ausgewählt) setAktionenDeaktiviert(); - return p; + + JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel); + split.setDividerLocation(300); + add(split, BorderLayout.CENTER); } - private int addLabelField(JPanel p, GridBagConstraints g, - 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 - // ------------------------------------------------------------------------- + // --- Daten --- private void ladeDokumente() { tableModel.setRowCount(0); - for (Dokument d : tracking.findAll()) { + List alle = dokumentenService.getAllDokumente(); + for (Dokument d : alle) { tableModel.addRow(new Object[]{ - d.getDokumentNummer(), - typBezeichnung(d.getTyp()), - d.getStatus().name() + d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer() + d.getTypBezeichnung(), + d.getStatus().getAnzeigename() }); } } private void zeigeAusgewaehltes() { int row = dokumentTable.getSelectedRow(); - if (row < 0) { aktuellesDokument = null; return; } + if (row < 0) return; int modelRow = dokumentTable.convertRowIndexToModel(row); String nr = (String) tableModel.getValueAt(modelRow, 0); - tracking.findByNummer(nr).ifPresent(this::zeigeDokument); - } + dokumentenService.getDokumentByNummer(nr).ifPresent(d -> { + aktuellesDokument = d; + txtDokumentnummer.setText(d.getDokumentNummer()); // FIX + txtKundenId.setText(String.valueOf(d.getKundenId())); // FIX + txtDatum.setText(d.getDatum().toString()); + txtStatus.setText(d.getStatus().getAnzeigename()); - private void zeigeDokument(Dokument d) { - 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()); + chkFestgeschrieben.setSelected(d.isFestgeschrieben()); - 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[]{ + // Positionen laden + posTableModel.setRowCount(0); + int pos = 1; + for (var position : d.getPositionen()) { + posTableModel.addRow(new Object[]{ pos++, - p.getBezeichnungSnapshot(), - p.getMenge(), - lieferschein ? "—" : (p.getEinzelpreisNettoSnapshot() + " €"), - lieferschein ? "—" : (formatProzent(p.getMwstSatzSnapshot())), - lieferschein ? "—" : (p.getNettoGesamt() + " €") - }); - } + position.getBezeichnungSnapshot(), // FIX + position.getMenge(), + position.getEinzelpreisNettoSnapshot() + " €", // FIX + position.getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100)) + .setScale(0, java.math.RoundingMode.HALF_UP) + "%",// FIX + position.getNettoGesamt() + " €" // FIX + }); + } - // 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())+ " €"); - } + // Summen (nicht für Lieferschein) + if (d.getNettoSumme() != null) { // FIX + lblNetto.setText(d.getNettoSumme().toPlainString() + " €"); // FIX + lblUst.setText(d.getUstSumme().toPlainString() + " €"); + lblBrutto.setText(d.getBruttoSumme().toPlainString() + " €"); // FIX + } - aktualisiereAktionen(d); + aktualisiereAktionen(d); + }); } - private String nullSichBd(BigDecimal v) { return v == null ? "0.00" : v.toPlainString(); } - /** - * Aktiviert nur die zulässigen Folgeaktionen basierend auf - * Dokumenttyp und -status (PHGUI10). - * Festgeschriebene Rechnungen: alle Aktionen außer PDF-Export - * deaktiviert (PHGUI11). + * Aktiviert nur zulässige Folgeaktionen basierend auf Dokumentstatus (PHGUI10). + * Festgeschriebene Rechnungen: nur PDF-Export aktiv (PHGUI11). */ private void aktualisiereAktionen(Dokument d) { - DokumentType typ = d.getTyp(); DokumentStatus status = d.getStatus(); + boolean festgeschrieben = d.isFestgeschrieben(); - if (status == DokumentStatus.FESTGESCHRIEBEN) { + // PHGUI11: Festgeschriebene Rechnung → nur PDF-Export + if (festgeschrieben) { + btnNeu.setEnabled(true); + btnSpeichern.setEnabled(false); + btnLoeschen.setEnabled(false); + btnAbbrechen.setEnabled(true); + btnPdfExport.setEnabled(true); // PHGUI12 btnAngebotZuAuftrag.setEnabled(false); btnAuftragZuLieferschein.setEnabled(false); btnLieferscheinZuRechnung.setEnabled(false); - btnPdfExport.setEnabled(true); // PHGUI12 return; } - 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); + // PHGUI10: Folgeaktionen je nach Status (FIX: korrekte Status-Konstanten) + btnNeu.setEnabled(true); + btnSpeichern.setEnabled(true); + btnLoeschen.setEnabled(true); + btnAbbrechen.setEnabled(true); + btnPdfExport.setEnabled(status == DokumentStatus.FESTGESCHRIEBEN); // FIX + + btnAngebotZuAuftrag.setEnabled(status == DokumentStatus.OFFEN); // FIX + btnAuftragZuLieferschein.setEnabled(status == DokumentStatus.BESTAETIGT); // FIX + btnLieferscheinZuRechnung.setEnabled(status == DokumentStatus.GELIEFERT); // FIX } 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; - dokumentTable.clearSelection(); - txtDokumentNummer.setText(""); - txtKundenId.setText(""); - txtDatum.setText(""); - txtStatus.setText(""); + 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(); } - /** - * Erzeugt das spec-konforme Folgedokument zum aktuell ausgewählten - * Dokument. PH-DP-04 / PH-DP-06 / PH-DP-08. - */ - private void folgeaktion(DokumentType erwartet) { + private void loeschen() { if (aktuellesDokument == null) { mainFrame.showStatus("Kein Dokument ausgewählt."); return; } - if (aktuellesDokument.getTyp() != erwartet) { - mainFrame.showStatus("Aktion passt nicht zum Dokumenttyp."); - return; + 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()); // FIX + if (deleted) { + ladeDokumente(); neuesFormular(); + mainFrame.showStatus("Dokument gelöscht."); + } else { + mainFrame.showStatus("Dokument kann nicht gelöscht werden (z. B. festgeschriebene Rechnung)."); } - String quellNr = aktuellesDokument.getDokumentNummer(); + } + + private void folgeaktion(String typ) { + if (aktuellesDokument == null) return; try { - 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; } + switch (typ) { + case "ANGEBOT_ZU_AUFTRAG" -> + dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX + case "AUFTRAG_ZU_LIEFERSCHEIN" -> + dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX + case "LIEFERSCHEIN_ZU_RECHNUNG" -> + dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX } ladeDokumente(); - mainFrame.showStatus("Folgedokument erstellt: " + folge.getDokumentNummer()); - } catch (RuntimeException ex) { - // Fängt IllegalStateException, DuplicateDocumentException, - // ValidationException, DokumentNotFoundException + mainFrame.showStatus("Folgedokument erfolgreich erstellt."); + } catch (Exception ex) { mainFrame.showStatus("Fehler: " + ex.getMessage()); } } @@ -427,128 +361,18 @@ public class DokumentPanel extends JPanel { return; } try { - Path pfad = dokumentService.exportiereAlsPdf( - aktuellesDokument.getDokumentNummer()); // PHGUI12 / PH-DP-12 - mainFrame.showStatus("PDF erzeugt: " + pfad); - } catch (RuntimeException ex) { + dokumentenService.exportPdf( + aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX + mainFrame.showStatus("PDF-Export erfolgreich."); + } catch (Exception 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 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 alle = new ArrayList<>(); - - TrackingDokumentRepository(DokumentRepository delegate) { - this.delegate = delegate; - } - - List findAll() { - List 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 findByNummer(String nummer) { return delegate.findByNummer(nummer); } - @Override public List 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); } - } -} \ No newline at end of file +}