25.06.2026 09:52 Commit Lastenheft 1.1 Pflichtenheft 1.0 new versions
parent
576220c381
commit
469acd2060
|
|
@ -50,8 +50,8 @@ public class DokumentenService {
|
|||
* Vollständiger Konstruktor – für Produktion wenn echte Lookups vorliegen.
|
||||
*/
|
||||
public DokumentenService(DokumentRepository repository,
|
||||
ProductLookup produktLookup,
|
||||
CustomerLookup kundeLookup) {
|
||||
ProductLookup produktLookup,
|
||||
CustomerLookup kundeLookup) {
|
||||
this.repository = repository;
|
||||
this.service = new DokumentServiceImpl(
|
||||
repository, produktLookup, kundeLookup, new SimplePdfExportService()
|
||||
|
|
@ -72,6 +72,16 @@ public class DokumentenService {
|
|||
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.
|
||||
* @return true wenn erfolgreich gelöscht
|
||||
|
|
@ -140,4 +150,4 @@ public class DokumentenService {
|
|||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,17 @@ package de.hsmannheim.faktura.gui;
|
|||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
import java.awt.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
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.DokumentStatus;
|
||||
import de.hsmannheim.faktura.dokumente.domain.Lieferschein;
|
||||
import de.hsmannheim.faktura.dokumente.service.DokumentenService;
|
||||
import de.hsmannheim.faktura.dokumente.service.PositionCommand;
|
||||
import de.hsmannheim.faktura.dokumente.repository.InMemoryDokumentRepository;
|
||||
|
||||
/**
|
||||
|
|
@ -61,9 +67,48 @@ public class DokumentPanel extends JPanel {
|
|||
setLayout(new BorderLayout(8, 8));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
initComponents();
|
||||
initialisiereDemoDaten();
|
||||
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() {
|
||||
// --- Linke Seite: Dokumentenliste ---
|
||||
JPanel leftPanel = new JPanel(new BorderLayout(4, 4));
|
||||
|
|
@ -192,7 +237,8 @@ public class DokumentPanel extends JPanel {
|
|||
rightPanel.add(aktionenPanel, BorderLayout.SOUTH);
|
||||
|
||||
// Button-Actions
|
||||
btnNeu.addActionListener(e -> neuesFormular());
|
||||
btnNeu.addActionListener(e -> neuesAngebotDialog());
|
||||
btnSpeichern.addActionListener(e -> neuesAngebotDialog());
|
||||
btnAbbrechen.addActionListener(e -> { neuesFormular(); ladeDokumente(); });
|
||||
btnPdfExport.addActionListener(e -> pdfExport());
|
||||
btnLoeschen.addActionListener(e -> loeschen());
|
||||
|
|
@ -215,9 +261,9 @@ public class DokumentPanel extends JPanel {
|
|||
List<Dokument> alle = dokumentenService.getAllDokumente();
|
||||
for (Dokument d : alle) {
|
||||
tableModel.addRow(new Object[]{
|
||||
d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer()
|
||||
d.getTypBezeichnung(),
|
||||
d.getStatus().getAnzeigename()
|
||||
d.getDokumentNummer(), // FIX: getDokumentNummer() statt getDokumentnummer()
|
||||
d.getTypBezeichnung(),
|
||||
d.getStatus().getAnzeigename()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -242,13 +288,13 @@ public class DokumentPanel extends JPanel {
|
|||
int pos = 1;
|
||||
for (var position : d.getPositionen()) {
|
||||
posTableModel.addRow(new Object[]{
|
||||
pos++,
|
||||
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
|
||||
pos++,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -323,8 +369,8 @@ public class DokumentPanel extends JPanel {
|
|||
return;
|
||||
}
|
||||
int confirm = JOptionPane.showConfirmDialog(
|
||||
this, "Dokument wirklich löschen?", "Löschen bestätigen",
|
||||
JOptionPane.YES_NO_OPTION
|
||||
this, "Dokument wirklich löschen?", "Löschen bestätigen",
|
||||
JOptionPane.YES_NO_OPTION
|
||||
);
|
||||
if (confirm != JOptionPane.YES_OPTION) return;
|
||||
|
||||
|
|
@ -342,11 +388,11 @@ public class DokumentPanel extends JPanel {
|
|||
try {
|
||||
switch (typ) {
|
||||
case "ANGEBOT_ZU_AUFTRAG" ->
|
||||
dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
dokumentenService.createAuftragsbestaetigung(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
case "AUFTRAG_ZU_LIEFERSCHEIN" ->
|
||||
dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
dokumentenService.createLieferschein(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
case "LIEFERSCHEIN_ZU_RECHNUNG" ->
|
||||
dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
dokumentenService.createRechnung(aktuellesDokument.getDokumentNummer()); // FIX
|
||||
}
|
||||
ladeDokumente();
|
||||
mainFrame.showStatus("Folgedokument erfolgreich erstellt.");
|
||||
|
|
@ -362,7 +408,7 @@ public class DokumentPanel extends JPanel {
|
|||
}
|
||||
try {
|
||||
dokumentenService.exportPdf(
|
||||
aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX
|
||||
aktuellesDokument.getDokumentNummer(), aktuellesDokument.getTyp()); // FIX
|
||||
mainFrame.showStatus("PDF-Export erfolgreich.");
|
||||
} catch (Exception ex) {
|
||||
mainFrame.showStatus("PDF-Export fehlgeschlagen: " + ex.getMessage());
|
||||
|
|
@ -375,4 +421,140 @@ public class DokumentPanel extends JPanel {
|
|||
f.setBackground(new Color(230, 230, 230));
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue