25.06.2026 09:52 Commit Lastenheft 1.1 Pflichtenheft 1.0 new versions

main
christopherlampert 2026-06-29 11:56:01 +02:00
parent 576220c381
commit 469acd2060
3 changed files with 213 additions and 21 deletions

View File

@ -50,8 +50,8 @@ public class DokumentenService {
* Vollständiger Konstruktor für Produktion wenn echte Lookups vorliegen. * Vollständiger Konstruktor für Produktion wenn echte Lookups vorliegen.
*/ */
public DokumentenService(DokumentRepository repository, public DokumentenService(DokumentRepository repository,
ProductLookup produktLookup, ProductLookup produktLookup,
CustomerLookup kundeLookup) { CustomerLookup kundeLookup) {
this.repository = repository; this.repository = repository;
this.service = new DokumentServiceImpl( this.service = new DokumentServiceImpl(
repository, produktLookup, kundeLookup, new SimplePdfExportService() repository, produktLookup, kundeLookup, new SimplePdfExportService()
@ -72,6 +72,16 @@ public class DokumentenService {
return repository.findByNummer(nummer); return repository.findByNummer(nummer);
} }
/**
* Neues Angebot anlegen (PH-DP-01).
* @param kundenId Kunde (muss in Kundenverwaltung existieren / vom CustomerLookup auflösbar sein)
* @param positionen mindestens eine Position (Produkt-ID + Menge)
* @return das gespeicherte Angebot mit Nummer und Status OFFEN
*/
public Angebot createAngebot(long kundenId, List<PositionCommand> positionen) {
return service.erstelleAngebot(kundenId, positionen);
}
/** /**
* Dokument löschen. Festgeschriebene Rechnungen können nicht gelöscht werden. * Dokument löschen. Festgeschriebene Rechnungen können nicht gelöscht werden.
* @return true wenn erfolgreich gelöscht * @return true wenn erfolgreich gelöscht
@ -140,4 +150,4 @@ public class DokumentenService {
)); ));
} }
} }
} }

View File

@ -3,11 +3,17 @@ package de.hsmannheim.faktura.gui;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.*; import javax.swing.table.*;
import java.awt.*; import java.awt.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.hsmannheim.faktura.dokumente.domain.Angebot;
import de.hsmannheim.faktura.dokumente.domain.Auftragsbestaetigung;
import de.hsmannheim.faktura.dokumente.domain.Dokument; import de.hsmannheim.faktura.dokumente.domain.Dokument;
import de.hsmannheim.faktura.dokumente.domain.DokumentStatus; import de.hsmannheim.faktura.dokumente.domain.DokumentStatus;
import de.hsmannheim.faktura.dokumente.domain.Lieferschein;
import de.hsmannheim.faktura.dokumente.service.DokumentenService; import de.hsmannheim.faktura.dokumente.service.DokumentenService;
import de.hsmannheim.faktura.dokumente.service.PositionCommand;
import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository; import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository;
/** /**
@ -61,9 +67,48 @@ public class DokumentPanel extends JPanel {
setLayout(new BorderLayout(8, 8)); setLayout(new BorderLayout(8, 8));
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
initComponents(); initComponents();
initialisiereDemoDaten();
ladeDokumente(); ladeDokumente();
} }
/**
* Legt beim Start einige Beispieldokumente an, damit der gesamte
* Belegfluss (Angebot AB Lieferschein Rechnung) sofort
* demonstriert werden kann.
*/
private void initialisiereDemoDaten() {
try {
// 1) Frisches Angebot (Status OFFEN) Folgeaktion "→ AB" wird aktiv
dokumentenService.createAngebot(1001L, List.of(
new PositionCommand(1L, 5),
new PositionCommand(2L, 3)));
// 2) Angebot überführt in AB (Status BESTAETIGT) Folgeaktion "→ Lieferschein" wird aktiv
Angebot a2 = dokumentenService.createAngebot(1002L,
List.of(new PositionCommand(1L, 10)));
dokumentenService.createAuftragsbestaetigung(a2.getDokumentNummer());
// 3) Kette bis Lieferschein (Status GELIEFERT) Folgeaktion "→ Rechnung" wird aktiv
Angebot a3 = dokumentenService.createAngebot(1003L,
List.of(new PositionCommand(3L, 2)));
Auftragsbestaetigung ab3 =
dokumentenService.createAuftragsbestaetigung(a3.getDokumentNummer());
dokumentenService.createLieferschein(ab3.getDokumentNummer());
// 4) Komplette Kette bis Rechnung (Status FESTGESCHRIEBEN) nur PDF-Export aktiv
Angebot a4 = dokumentenService.createAngebot(1004L,
List.of(new PositionCommand(1L, 1)));
Auftragsbestaetigung ab4 =
dokumentenService.createAuftragsbestaetigung(a4.getDokumentNummer());
Lieferschein ls4 =
dokumentenService.createLieferschein(ab4.getDokumentNummer());
dokumentenService.createRechnung(ls4.getDokumentNummer());
} catch (Exception ex) {
System.err.println("Demo-Daten konnten nicht initialisiert werden: "
+ ex.getMessage());
}
}
private void initComponents() { private void initComponents() {
// --- Linke Seite: Dokumentenliste --- // --- Linke Seite: Dokumentenliste ---
JPanel leftPanel = new JPanel(new BorderLayout(4, 4)); JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
@ -192,7 +237,8 @@ public class DokumentPanel extends JPanel {
rightPanel.add(aktionenPanel, BorderLayout.SOUTH); rightPanel.add(aktionenPanel, BorderLayout.SOUTH);
// Button-Actions // Button-Actions
btnNeu.addActionListener(e -> neuesFormular()); btnNeu.addActionListener(e -> neuesAngebotDialog());
btnSpeichern.addActionListener(e -> neuesAngebotDialog());
btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); }); btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); });
btnPdfExport.addActionListener(e -> pdfExport()); btnPdfExport.addActionListener(e -> pdfExport());
btnLoeschen.addActionListener(e -> loeschen()); btnLoeschen.addActionListener(e -> loeschen());
@ -215,9 +261,9 @@ public class DokumentPanel extends JPanel {
List<Dokument> alle = dokumentenService.getAllDokumente(); List<Dokument> alle = dokumentenService.getAllDokumente();
for (Dokument d : alle) { for (Dokument d : alle) {
tableModel.addRow(new Object[]{ tableModel.addRow(new Object[]{
d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer() d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer()
d.getTypBezeichnung(), d.getTypBezeichnung(),
d.getStatus().getAnzeigename() d.getStatus().getAnzeigename()
}); });
} }
} }
@ -242,13 +288,13 @@ public class DokumentPanel extends JPanel {
int pos = 1; int pos = 1;
for (var position : d.getPositionen()) { for (var position : d.getPositionen()) {
posTableModel.addRow(new Object[]{ posTableModel.addRow(new Object[]{
pos++, pos++,
position.getBezeichnungSnapshot(), // FIX position.getBezeichnungSnapshot(), // FIX
position.getMenge(), position.getMenge(),
position.getEinzelpreisNettoSnapshot() + " €", // FIX position.getEinzelpreisNettoSnapshot() + " €", // FIX
position.getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100)) position.getMwstSatzSnapshot().multiply(java.math.BigDecimal.valueOf(100))
.setScale(0, java.math.RoundingMode.HALF_UP) + "%",// FIX .setScale(0, java.math.RoundingMode.HALF_UP) + "%",// FIX
position.getNettoGesamt() + " €" // FIX position.getNettoGesamt() + " €" // FIX
}); });
} }
@ -323,8 +369,8 @@ public class DokumentPanel extends JPanel {
return; return;
} }
int confirm = JOptionPane.showConfirmDialog( int confirm = JOptionPane.showConfirmDialog(
this, "Dokument wirklich löschen?", "Löschen bestätigen", this, "Dokument wirklich löschen?", "Löschen bestätigen",
JOptionPane.YES_NO_OPTION JOptionPane.YES_NO_OPTION
); );
if (confirm != JOptionPane.YES_OPTION) return; if (confirm != JOptionPane.YES_OPTION) return;
@ -342,11 +388,11 @@ public class DokumentPanel extends JPanel {
try { try {
switch (typ) { switch (typ) {
case "ANGEBOT_ZU_AUFTRAG" -> case "ANGEBOT_ZU_AUFTRAG" ->
dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX
case "AUFTRAG_ZU_LIEFERSCHEIN" -> case "AUFTRAG_ZU_LIEFERSCHEIN" ->
dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX
case "LIEFERSCHEIN_ZU_RECHNUNG" -> case "LIEFERSCHEIN_ZU_RECHNUNG" ->
dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX
} }
ladeDokumente(); ladeDokumente();
mainFrame.showStatus("Folgedokument erfolgreich erstellt."); mainFrame.showStatus("Folgedokument erfolgreich erstellt.");
@ -362,7 +408,7 @@ public class DokumentPanel extends JPanel {
} }
try { try {
dokumentenService.exportPdf( dokumentenService.exportPdf(
aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX
mainFrame.showStatus("PDF-Export erfolgreich."); mainFrame.showStatus("PDF-Export erfolgreich.");
} catch (Exception ex) { } catch (Exception ex) {
mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage()); mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage());
@ -375,4 +421,140 @@ public class DokumentPanel extends JPanel {
f.setBackground(new Color(230, 230, 230)); f.setBackground(new Color(230, 230, 230));
return f; return f;
} }
}
// -------------------------------------------------------------------------
// Dialog: Neues Angebot anlegen
// -------------------------------------------------------------------------
/**
* Öffnet einen modalen Dialog zum Anlegen eines neuen Angebots.
* Eingaben: Kunden-ID und mindestens eine Position (Produkt-ID + Menge).
* Bei erfolgreichem Anlegen wird das Angebot gespeichert und die
* Dokumentenliste neu geladen.
*/
private void neuesAngebotDialog() {
JDialog dialog = new JDialog(
(Frame) SwingUtilities.getWindowAncestor(this),
"Neues Angebot anlegen", true);
dialog.setLayout(new BorderLayout(8, 8));
((JComponent) dialog.getContentPane())
.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
// --- Kopfbereich: Kunden-ID ---
JPanel headPanel = new JPanel(new GridBagLayout());
headPanel.setBorder(BorderFactory.createTitledBorder("Kunde"));
GridBagConstraints g = new GridBagConstraints();
g.insets = new Insets(3, 4, 3, 4);
g.fill = GridBagConstraints.HORIZONTAL;
g.weightx = 1.0;
g.gridx = 0; g.gridy = 0; headPanel.add(new JLabel("Kunden-ID:"), g);
JTextField txtKundenIdInput = new JTextField("1001");
g.gridx = 1; headPanel.add(txtKundenIdInput, g);
// --- Positionsbereich ---
String[] cols = {"Produkt-ID", "Menge"};
DefaultTableModel posModel = new DefaultTableModel(cols, 0) {
@Override
public Class<?> getColumnClass(int column) { return String.class; }
@Override
public boolean isCellEditable(int r, int c) { return true; }
};
JTable tblPositionen = new JTable(posModel);
tblPositionen.setRowHeight(22);
// Eine Beispielzeile zum Loslegen
posModel.addRow(new Object[]{"1", "1"});
JPanel posButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton btnPosHinzu = new JButton("Position hinzufügen");
JButton btnPosEntfernen = new JButton("Position entfernen");
posButtons.add(btnPosHinzu);
posButtons.add(btnPosEntfernen);
btnPosHinzu.addActionListener(ev -> posModel.addRow(new Object[]{"1", "1"}));
btnPosEntfernen.addActionListener(ev -> {
int row = tblPositionen.getSelectedRow();
if (row >= 0) posModel.removeRow(row);
});
JPanel posPanel = new JPanel(new BorderLayout(4, 4));
posPanel.setBorder(BorderFactory.createTitledBorder("Positionen"));
posPanel.add(new JScrollPane(tblPositionen), BorderLayout.CENTER);
posPanel.add(posButtons, BorderLayout.SOUTH);
// --- OK / Abbrechen ---
JButton btnOk = new JButton("Anlegen");
JButton btnCancel = new JButton("Abbrechen");
JPanel dlgButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
dlgButtons.add(btnOk);
dlgButtons.add(btnCancel);
JLabel lblFehler = new JLabel(" ");
lblFehler.setForeground(new Color(192, 57, 43));
btnCancel.addActionListener(ev -> dialog.dispose());
btnOk.addActionListener(ev -> {
// Bei in der Tabelle laufender Bearbeitung: Wert übernehmen
if (tblPositionen.isEditing()) tblPositionen.getCellEditor().stopCellEditing();
// Kunden-ID parsen
long kundenId;
try {
kundenId = Long.parseLong(txtKundenIdInput.getText().trim());
} catch (NumberFormatException ex) {
lblFehler.setText("Kunden-ID muss eine Zahl sein.");
return;
}
// Positionen aufbauen
List<PositionCommand> positionen = new ArrayList<>();
for (int r = 0; r < posModel.getRowCount(); r++) {
Object pidObj = posModel.getValueAt(r, 0);
Object mengeObj = posModel.getValueAt(r, 1);
if (pidObj == null || mengeObj == null
|| pidObj.toString().isBlank() || mengeObj.toString().isBlank()) {
continue;
}
try {
long pid = Long.parseLong(pidObj.toString().trim());
int menge = Integer.parseInt(mengeObj.toString().trim());
positionen.add(new PositionCommand(pid, menge));
} catch (NumberFormatException ex) {
lblFehler.setText("Position " + (r + 1)
+ ": Produkt-ID und Menge müssen Zahlen sein.");
return;
} catch (IllegalArgumentException ex) {
lblFehler.setText("Position " + (r + 1) + ": " + ex.getMessage());
return;
}
}
if (positionen.isEmpty()) {
lblFehler.setText("Mindestens eine Position erforderlich.");
return;
}
// Angebot anlegen
try {
Angebot a = dokumentenService.createAngebot(kundenId, positionen);
ladeDokumente();
mainFrame.showStatus("Angebot " + a.getDokumentNummer()
+ " angelegt (Netto " + a.getNettoSumme() + " €).");
dialog.dispose();
} catch (Exception ex) {
lblFehler.setText("Fehler: " + ex.getMessage());
}
});
JPanel southPanel = new JPanel(new BorderLayout());
southPanel.add(lblFehler, BorderLayout.NORTH);
southPanel.add(dlgButtons, BorderLayout.SOUTH);
dialog.add(headPanel, BorderLayout.NORTH);
dialog.add(posPanel, BorderLayout.CENTER);
dialog.add(southPanel, BorderLayout.SOUTH);
dialog.setSize(480, 360);
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
}