Knopf "Neues Projekt" zum Leeren des Arbeitsstands

Bisher liess sich ein neuer Auftrag nur von Hand beginnen: Namen
ueberschreiben, jede Position neu ausfuellen und auf der Rechnungsseite
Kundendaten und Rechnungsnummer leeren. Wurde der letzte Schritt
vergessen, stand der alte Kunde auf der neuen Rechnung.

- POST /projekte/neu setzt nur PROJEKT_FELDER zurueck (Name, Positionen,
  Kundendaten, Rechnungsnummer, Datum auf heute). Betriebswerte wie
  Marge, Stundenlohn, Drucker, Steuer und Firmendaten bleiben.
- GET /projekte liefert zusaetzlich 'ungespeichert'. Der Knopf fragt nur
  nach, wenn der offene Stand wirklich nicht als Projekt gesichert ist.
- Der Vergleich ignoriert 'datum' und 'geaendert', sonst waere ein
  gespeichertes Projekt am naechsten Tag faelschlich "ungespeichert".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik Thiele
2026-08-22 11:59:20 +02:00
parent ce3ba9df7e
commit 1b3ef82db4
5 changed files with 106 additions and 1 deletions

65
app.py
View File

@@ -359,6 +359,50 @@ def projekt_aus_config(config):
for feld in PROJEKT_FELDER}
def leerer_projektstand():
"""Projektfelder im Ausgangszustand. Datum auf heute."""
stand = {feld: DEFAULT_VALUES.get(feld) for feld in PROJEKT_FELDER}
stand['projektName'] = ''
stand['kundenName'] = ''
stand['kundenAdresse'] = ''
stand['rechnungsnummer'] = ''
stand['leistungsdatum'] = ''
stand['datum'] = datetime.now().strftime('%Y-%m-%d')
stand['positionen'] = [dict(DEFAULT_POSITION)]
return stand
def _vergleichbar(stand):
"""Projektfelder in eine vergleichbare Form bringen.
'geaendert' und das Datum bleiben aussen vor -- ein neuer Tag macht
aus einem gespeicherten Projekt keinen ungespeicherten Stand.
"""
return {
'projektName': projekt_name(stand.get('projektName')),
'kundenName': str(stand.get('kundenName') or ''),
'kundenAdresse': str(stand.get('kundenAdresse') or ''),
'rechnungsnummer': str(stand.get('rechnungsnummer') or ''),
'leistungsdatum': str(stand.get('leistungsdatum') or ''),
'positionen': saubere_positionen(stand.get('positionen')),
}
def ungespeicherte_aenderungen(config, projekte):
"""True, wenn der Arbeitsstand nicht so in den Projekten liegt.
Ein voellig leerer Stand zaehlt nicht als ungespeichert -- da ist
nichts zu verlieren.
"""
aktuell = _vergleichbar(config)
if aktuell == _vergleichbar(leerer_projektstand()):
return False
gespeichert = projekte.get(aktuell['projektName'])
if not isinstance(gespeichert, dict):
return True
return aktuell != _vergleichbar(gespeichert)
def steuer_kontext(data):
"""Liest Regime und MwSt.-Satz robust aus den Formularwerten."""
regime = str(data.get('steuerregime', 'regel')).lower()
@@ -582,7 +626,11 @@ def projekte_liste():
'gesamt_endpreis': calc['gesamt_endpreis'],
})
eintraege.sort(key=lambda e: e['geaendert'], reverse=True)
return jsonify({'projekte': eintraege, 'aktuell': config.get('projektName', '')})
return jsonify({
'projekte': eintraege,
'aktuell': config.get('projektName', ''),
'ungespeichert': ungespeicherte_aenderungen(config, projekte),
})
@app.route('/projekte/speichern', methods=['POST'])
@@ -635,6 +683,21 @@ def projekt_laden():
return jsonify({'status': 'ok', 'name': name})
@app.route('/projekte/neu', methods=['POST'])
def projekt_neu():
"""Setzt den Arbeitsstand auf einen leeren Auftrag zurueck.
Betriebswerte (Drucker, Strom, Stundenlohn, Marge, Steuer,
Firmendaten) bleiben unangetastet -- zurueckgesetzt wird nur, was
zu einem einzelnen Auftrag gehoert.
"""
with _config_lock:
config = load_config()
config.update(leerer_projektstand())
save_config(_migriere(config))
return jsonify({'status': 'ok'})
@app.route('/projekte/loeschen', methods=['POST'])
def projekt_loeschen():
"""Entfernt ein gespeichertes Projekt. Der Arbeitsstand bleibt."""