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:
@@ -92,6 +92,10 @@ Kein Test, Lint, Typecheck oder CI vorhanden. README.md und AGENTS.md enthalten
|
|||||||
Die Preise in der Projektliste werden mit den AKTUELLEN Betriebswerten
|
Die Preise in der Projektliste werden mit den AKTUELLEN Betriebswerten
|
||||||
gerechnet, nicht mit denen von damals.
|
gerechnet, nicht mit denen von damals.
|
||||||
- `druckkalkulation_config.yaml` ist der Arbeitsstand, nicht das Archiv.
|
- `druckkalkulation_config.yaml` ist der Arbeitsstand, nicht das Archiv.
|
||||||
|
- `/projekte/neu` setzt nur `PROJEKT_FELDER` zurueck. `GET /projekte`
|
||||||
|
liefert zusaetzlich `ungespeichert` -- der Vergleich laeuft ueber
|
||||||
|
`_vergleichbar()` und ignoriert bewusst `datum` und `geaendert`, sonst
|
||||||
|
waere ein Projekt am naechsten Tag faelschlich "ungespeichert".
|
||||||
- Alte Configs mit den Flachfeldern (`materialGewicht` etc.) migriert
|
- Alte Configs mit den Flachfeldern (`materialGewicht` etc.) migriert
|
||||||
`_migriere()` in eine einzelne Position und entfernt die Altfelder.
|
`_migriere()` in eine einzelne Position und entfernt die Altfelder.
|
||||||
- **Saeamtliche UI-Texte sind Deutsch** — Labels, Fehlermeldungen, Rechnungsinhalte.
|
- **Saeamtliche UI-Texte sind Deutsch** — Labels, Fehlermeldungen, Rechnungsinhalte.
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ sich ueber `HOST` und `PORT` ueberschreiben.
|
|||||||
Positionsliste kalkuliert und als eigene Rechnungszeilen ausgewiesen.
|
Positionsliste kalkuliert und als eigene Rechnungszeilen ausgewiesen.
|
||||||
Material, Preis/kg, Druckzeit, Arbeitszeit und Stückzahl gelten je Position;
|
Material, Preis/kg, Druckzeit, Arbeitszeit und Stückzahl gelten je Position;
|
||||||
Drucker, Strom, Stundenlohn, Marge und Steuer gelten für den ganzen Auftrag.
|
Drucker, Strom, Stundenlohn, Marge und Steuer gelten für den ganzen Auftrag.
|
||||||
|
- **Neues Projekt** — leert Projektname, Positionen, Kundendaten und
|
||||||
|
Rechnungsnummer; warnt vorher, wenn der offene Stand nicht gesichert ist
|
||||||
- **Projekte speichern und laden** — benannte Kalkulationen bleiben erhalten
|
- **Projekte speichern und laden** — benannte Kalkulationen bleiben erhalten
|
||||||
und lassen sich jederzeit wieder aufrufen (`druckkalkulation_projekte.yaml`).
|
und lassen sich jederzeit wieder aufrufen (`druckkalkulation_projekte.yaml`).
|
||||||
Betriebswerte (Drucker, Strom, Stundenlohn, Marge, Steuer) gehören *nicht*
|
Betriebswerte (Drucker, Strom, Stundenlohn, Marge, Steuer) gehören *nicht*
|
||||||
@@ -139,6 +141,7 @@ druckkalkulation_projekte.yaml # Gespeicherte Projekte (ignoriert)
|
|||||||
| POST | `/projekte/speichern` | Aktuellen Stand als Projekt sichern |
|
| POST | `/projekte/speichern` | Aktuellen Stand als Projekt sichern |
|
||||||
| POST | `/projekte/laden` | Projekt in den Arbeitsstand laden |
|
| POST | `/projekte/laden` | Projekt in den Arbeitsstand laden |
|
||||||
| POST | `/projekte/loeschen` | Projekt entfernen |
|
| POST | `/projekte/loeschen` | Projekt entfernen |
|
||||||
|
| POST | `/projekte/neu` | Arbeitsstand auf leeren Auftrag zurücksetzen |
|
||||||
| POST | `/upload_logo` | Logo hochladen |
|
| POST | `/upload_logo` | Logo hochladen |
|
||||||
| POST | `/delete_logo` | Logo loschen |
|
| POST | `/delete_logo` | Logo loschen |
|
||||||
|
|
||||||
|
|||||||
65
app.py
65
app.py
@@ -359,6 +359,50 @@ def projekt_aus_config(config):
|
|||||||
for feld in PROJEKT_FELDER}
|
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):
|
def steuer_kontext(data):
|
||||||
"""Liest Regime und MwSt.-Satz robust aus den Formularwerten."""
|
"""Liest Regime und MwSt.-Satz robust aus den Formularwerten."""
|
||||||
regime = str(data.get('steuerregime', 'regel')).lower()
|
regime = str(data.get('steuerregime', 'regel')).lower()
|
||||||
@@ -582,7 +626,11 @@ def projekte_liste():
|
|||||||
'gesamt_endpreis': calc['gesamt_endpreis'],
|
'gesamt_endpreis': calc['gesamt_endpreis'],
|
||||||
})
|
})
|
||||||
eintraege.sort(key=lambda e: e['geaendert'], reverse=True)
|
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'])
|
@app.route('/projekte/speichern', methods=['POST'])
|
||||||
@@ -635,6 +683,21 @@ def projekt_laden():
|
|||||||
return jsonify({'status': 'ok', 'name': name})
|
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'])
|
@app.route('/projekte/loeschen', methods=['POST'])
|
||||||
def projekt_loeschen():
|
def projekt_loeschen():
|
||||||
"""Entfernt ein gespeichertes Projekt. Der Arbeitsstand bleibt."""
|
"""Entfernt ein gespeichertes Projekt. Der Arbeitsstand bleibt."""
|
||||||
|
|||||||
@@ -345,6 +345,37 @@ async function projektLaden() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Beginnt einen leeren Auftrag. Betriebswerte bleiben erhalten. */
|
||||||
|
async function projektNeu() {
|
||||||
|
// Offenen Autosave durchdruecken, damit der Server den wirklich
|
||||||
|
// aktuellen Stand kennt, bevor wir nach Ungespeichertem fragen.
|
||||||
|
await sofortSpeichern();
|
||||||
|
let offen = false;
|
||||||
|
try {
|
||||||
|
const daten = await (await fetch('/projekte')).json();
|
||||||
|
offen = !!daten.ungespeichert;
|
||||||
|
} catch (e) {
|
||||||
|
offen = true; // Im Zweifel lieber nachfragen.
|
||||||
|
}
|
||||||
|
if (offen && !confirm(
|
||||||
|
'Der aktuelle Stand ist nicht als Projekt gespeichert.\n\n' +
|
||||||
|
'Projektname, Positionen, Kundendaten und Rechnungsnummer werden ' +
|
||||||
|
'geleert. Betriebswerte (Marge, Drucker, Steuer) bleiben erhalten.\n\n' +
|
||||||
|
'Trotzdem neu anfangen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch('/projekte/neu', { method: 'POST' });
|
||||||
|
if (!res.ok) {
|
||||||
|
showNotification('Zuruecksetzen fehlgeschlagen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
location.reload();
|
||||||
|
} catch (e) {
|
||||||
|
showNotification('Zuruecksetzen fehlgeschlagen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Loescht das ausgewaehlte Projekt (der Arbeitsstand bleibt bestehen). */
|
/** Loescht das ausgewaehlte Projekt (der Arbeitsstand bleibt bestehen). */
|
||||||
async function projektLoeschen() {
|
async function projektLoeschen() {
|
||||||
const auswahl = document.getElementById('projekt-auswahl');
|
const auswahl = document.getElementById('projekt-auswahl');
|
||||||
|
|||||||
@@ -68,6 +68,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text" id="projekt-info">Noch keine Projekte gespeichert.</div>
|
<div class="form-text" id="projekt-info">Noch keine Projekte gespeichert.</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-outline-secondary w-100 mt-3" onclick="projektNeu()" title="Alle Auftragsdaten leeren">
|
||||||
|
<i class="ti ti-file-plus me-1"></i>Neues Projekt
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user