From f9993b4c73a15c174739a8d15f74399efb6ccb7d Mon Sep 17 00:00:00 2001 From: Stefan Zwischenbrugger Date: Sat, 4 Apr 2026 15:33:32 +0200 Subject: [PATCH] Kassabon-Korrekturen speichern und Listen-Scroll stabilisieren. Receipt-Scan: Artikelzeilen gehen mit Daten speichern in raw_meta; ein Formular fuer Kopf und Zeilen; OCR per separatem Form ohne Verschachtelung; apply-items Route akzeptiert POST und PATCH. Einkaufsliste: Scrollposition nach Erledigt-Toggle wiederherstellen und overflow-anchor am Zwei-Spalten-Grid abschalten. Made-with: Cursor --- .../Controllers/ReceiptScanController.php | 47 +++++++++++ .../Requests/UpdateReceiptScanRequest.php | 8 ++ resources/views/receipt-scans/index.blade.php | 80 +++++++++++-------- resources/views/shopping-list/index.blade.php | 65 ++++++++++++++- routes/web.php | 2 +- 5 files changed, 163 insertions(+), 39 deletions(-) diff --git a/app/Http/Controllers/ReceiptScanController.php b/app/Http/Controllers/ReceiptScanController.php index 8b42d5b..c5bb4f5 100644 --- a/app/Http/Controllers/ReceiptScanController.php +++ b/app/Http/Controllers/ReceiptScanController.php @@ -161,6 +161,53 @@ class ReceiptScanController extends Controller 'total_decimal' => $request->filled('total_decimal') ? $request->input('total_decimal') : null, ]); + if ($request->has('row_labels') && is_array($request->input('row_labels'))) { + $receiptScan->refresh(); + $meta = is_array($receiptScan->raw_meta) ? $receiptScan->raw_meta : []; + $baseSuggestions = $this->normalizeItemSuggestions( + $meta['item_suggestions'] ?? $this->extractItemSuggestions($receiptScan->ocr_text) + ); + $labels = $request->input('row_labels', []); + $prices = $request->input('row_prices', []); + $qtys = $request->input('row_qty', []); + $take = $request->input('row_take', []); + + $merged = []; + foreach ($labels as $i => $labelRaw) { + $label = trim((string) $labelRaw); + if ($label === '') { + continue; + } + $priceRaw = trim((string) ($prices[$i] ?? '')); + $qtyRaw = trim((string) ($qtys[$i] ?? '')); + $uncertain = (bool) ($baseSuggestions[$i]['is_uncertain'] ?? false); + $merged[] = [ + 'label' => $label, + 'price_raw' => $this->stripVatLetterFromPriceField($priceRaw), + 'quantity_raw' => $qtyRaw, + 'is_uncertain' => $uncertain, + ]; + } + + $selectionState = []; + foreach ($labels as $i => $labelRaw) { + $label = trim((string) $labelRaw); + if ($label === '') { + continue; + } + $priceRaw = trim((string) ($prices[$i] ?? '')); + $qtyRaw = trim((string) ($qtys[$i] ?? '')); + $selectionKey = $this->suggestionRowKey($label, $priceRaw, $qtyRaw); + $selectionState[$selectionKey] = isset($take[$i]); + } + + if ($merged !== []) { + $meta['item_suggestions'] = $merged; + $meta['item_selection_state'] = $selectionState; + $receiptScan->update(['raw_meta' => $meta]); + } + } + return back()->with('status', 'Kassazettel-Daten aktualisiert.'); } diff --git a/app/Http/Requests/UpdateReceiptScanRequest.php b/app/Http/Requests/UpdateReceiptScanRequest.php index f67f7fc..9e5537c 100644 --- a/app/Http/Requests/UpdateReceiptScanRequest.php +++ b/app/Http/Requests/UpdateReceiptScanRequest.php @@ -17,6 +17,14 @@ class UpdateReceiptScanRequest extends FormRequest 'store_name' => ['nullable', 'string', 'max:255'], 'receipt_date' => ['nullable', 'date'], 'total_decimal' => ['nullable', 'numeric', 'min:0', 'max:999999.99'], + 'row_labels' => ['nullable', 'array'], + 'row_labels.*' => ['nullable', 'string', 'max:255'], + 'row_prices' => ['nullable', 'array'], + 'row_prices.*' => ['nullable', 'string', 'max:64'], + 'row_qty' => ['nullable', 'array'], + 'row_qty.*' => ['nullable', 'string', 'max:64'], + 'row_take' => ['nullable', 'array'], + 'row_take.*' => ['nullable', 'in:1'], ]; } } diff --git a/resources/views/receipt-scans/index.blade.php b/resources/views/receipt-scans/index.blade.php index 7d933a3..5c9420d 100644 --- a/resources/views/receipt-scans/index.blade.php +++ b/resources/views/receipt-scans/index.blade.php @@ -92,44 +92,42 @@ · {{ $validatedAtFormatted }} @endif -
+ @csrf
+
+ + @if($ocrAvailable && ! $scan->ocr_text) + Ohne neues Foto; z. B. nach Tesseract-Pfad oder OCR-Update. + @endif +
+
@csrf @method('PATCH') -
- - +
+
+ + +
+
+ + +
+
+ + +
-
- - -
-
- - -
-
- -
- @if(! $scan->ocr_text && is_string($scan->raw_meta['hint'] ?? null) && $scan->raw_meta['hint'] !== '')
{{ $scan->raw_meta['hint'] }}
@endif -
-
- @csrf - -
- @if($ocrAvailable && ! $scan->ocr_text) - Ohne neues Foto; z. B. nach Tesseract-Pfad oder OCR-Update. - @endif -
OCR-Text anzeigen
{{ $scan->ocr_text ?: 'Kein OCR-Text vorhanden.' }}
@@ -166,10 +164,9 @@ Unsichere Treffer sind markiert und standardmaessig nicht angehakt. Bei Einrueckung (z. B. Biskotten, darunter „2 x 1,49“): Menge und Einzelpreis werden erkannt; Gesamt daneben ist nur Kontrolle. Buchstaben A/B/E hinter dem Betrag sind nur MwSt-Kennzeichen (kein Teil des Preises). + Aenderungen an Artikelzeilen mit Daten speichern sichern.

-
- @csrf -
+
@foreach($suggestions as $sug) @php $label = is_array($sug) ? ($sug['label'] ?? '') : (string) $sug; @@ -242,18 +239,31 @@
@endforeach
-
- +
-
@else

Keine sicheren Artikel erkannt. Du kannst OCR-Text manuell pruefen.

+
+ +
@endif
+ @if(($scan->raw_meta['error'] ?? null) === 'ocr_unavailable')

OCR war beim Upload nicht verfuegbar.

@endif diff --git a/resources/views/shopping-list/index.blade.php b/resources/views/shopping-list/index.blade.php index 54fb3c2..65d9f26 100644 --- a/resources/views/shopping-list/index.blade.php +++ b/resources/views/shopping-list/index.blade.php @@ -59,7 +59,7 @@ -
+

Offene Eintraege

@@ -69,7 +69,7 @@
@csrf @method('PATCH') @@ -131,7 +131,7 @@ @forelse($doneItems as $item)
- + @csrf @method('PATCH') @@ -364,6 +364,65 @@