77 KiB
Development Backlog - 22.01.2026
Status-Legende
[x]Erledigt[ ]Offen[!]Hohe Priorität[?]Klärungsbedarf
ERLEDIGTE AUFGABEN
[x] Produkt-Slugs anpassen
- Status: Erledigt
- Beschreibung: Slug kann direkt im Admin geändert werden
- URL: https://gesundheit.mivita.care/produkte/black-friday-week
[x] WWW-Redirect entfernen
- Status: Erledigt
- Beschreibung: Domain/Subdomain funktioniert ohne WWW-Prefix
[x] Abo-Anpassungen (Protokoll Claudia)
- Status: Erledigt
- Änderungen:
- Checkbox für AGB vor Abo-Abschluss
- Änderungen erst nach 6 Ausführungen möglich
- Nur Liefertag + Lieferadresse änderbar
- Lieferadresse sync mit Benutzerdaten
OFFENE AUFGABEN
[X] 1. NEWS: Download-Center Verlinkung
Priorität: Hoch ✅ ERLEDIGT Bereich: Dashboard / News
Problem: Benutzer finden das Download-Center nicht. Nur Verweis reicht nicht.
Lösung implementiert:
✅ Strukturiertes JSON-Feld file_links für Datei-Links pro Sprache
✅ Admin-Formular mit Select2-Dropdown zur Auswahl von DC-Dateien
✅ Mehrsprachige Unterstützung (DE, EN, ES)
✅ Schöne Button-Darstellung im Dashboard mit Icons
✅ Direkte Links zum Download-Center
Implementierte Dateien:
-
Migration:
2026_01_23_120458_add_file_links_to_dashboard_news_table.php- Neues JSON-Feld
file_linksindashboard_newsTabelle
- Neues JSON-Feld
-
Model:
app/Models/DashboardNews.phpfile_linkszu$fillableund$castshinzugefügt- Neue Methoden:
getFileLinks($lang),hasFileLinks($lang)
-
Admin-Formular:
resources/views/admin/site/news/form.blade.php- Datei-Link-Sektion für jede Sprache
- Select2-Dropdown mit allen aktiven DC-Dateien
- JavaScript zum Hinzufügen/Entfernen von Links
- Dynamische Label-Eingabe
-
Frontend-View:
resources/views/dashboard/_news.blade.php- Anzeige der Datei-Links als grüne Download-Buttons
- Direkter Download-Link über
route('storage_file', [$file->id, 'dc_file', 'download']) - Icons mit Ionicons
- Responsive Darstellung
-
Übersetzungen:
- DE:
resources/lang/de/backend.php - EN:
resources/lang/en/backend.php - ES:
resources/lang/es/backend.php - Neue Keys:
file_links,file_links_hint,link_label,select_file,add_file_link
- DE:
JSON-Struktur:
{
"de": [
{ "file_id": 123, "label": "Preisliste herunterladen" },
{ "file_id": 456, "label": "Produktkatalog öffnen" }
],
"en": [{ "file_id": 789, "label": "Download Price List" }]
}
Verwendung im Admin:
- News bearbeiten → Zum jeweiligen Sprach-Tab scrollen
- "Datei-Link hinzufügen" klicken
- Label eingeben (z.B. "Preisliste herunterladen")
- Datei aus Dropdown auswählen
- Speichern → Links erscheinen prominent im Dashboard
[X] 2. Points mit Dezimalstellen (DECIMAL statt INT)
Priorität: Hoch Bereich: Marketingplan / Provisionsberechnung
Problem: Punkte werden aktuell als INT gespeichert. Kommazahlen bei Produkten werden falsch berechnet/gerundet.
Anforderung:
- Alle Punkte-Felder auf DECIMAL umstellen
- Berechnung im gesamten Marketingplan anpassen
Punkte-Ursprung (Produkte):
| Parameter | Wert |
|---|---|
| Model | App\Models\Product |
| Feld | points (INT) → DECIMAL(10,2) |
| Zusätzlich | sponsor_buying_points, sponsor_buying_points_amount |
Punkte-Aggregation (Sales Volume):
| Parameter | Aktuell | Neu |
|---|---|---|
| Model | App\Models\UserSalesVolume |
- |
| Tabelle | user_sales_volumes |
- |
| Datentyp | INT |
DECIMAL(10,2) |
Betroffene Spalten user_sales_volumes:
ALTER TABLE user_sales_volumes
MODIFY points DECIMAL(10,2),
MODIFY month_points DECIMAL(10,2),
MODIFY month_KP_points DECIMAL(10,2),
MODIFY month_TP_points DECIMAL(10,2),
MODIFY month_shop_points DECIMAL(10,2);
Betroffene Spalten user_business:
ALTER TABLE user_business
MODIFY sales_volume_points DECIMAL(10,2),
MODIFY sales_volume_points_shop DECIMAL(10,2),
MODIFY sales_volume_points_sum DECIMAL(10,2);
Betroffene Spalten products:
ALTER TABLE products
MODIFY points DECIMAL(10,2);
Weitere Models mit points:
App\Models\ShoppingOrder→pointsApp\Models\ShoppingOrderItem→pointsApp\Models\ShoppingCollectOrder→pointsApp\Models\HomepartyUserOrderItem→points
Betroffene Services:
app/Services/BusinessPlan/TreeCalcBotOptimized.php- Alle Berechnungen in
app/Services/BusinessPlan/
Migration erforderlich: Ja (mehrere ALTER TABLE)
[X] 3. Vorkasse: Verwendungszweck deutlich machen
Priorität: Hoch Bereich: Checkout / Payment / E-Mail
Problem: Kunden geben falschen Verwendungszweck an. System kann Zahlung nicht zuordnen.
Anforderung:
- Payone TXID als Verwendungszweck deutlich hervorheben
- Hinweis im Checkout, in E-Mail und im Kundenkonto
Technische Details:
| Parameter | Wert |
|---|---|
| Clearingtype | vor (Vorkasse) |
| Controller | App\Http\Controllers\Pay\PayoneController |
| Checkout-View | resources/views/web/templates/checkout.blade.php:898 |
| Mail-Template | resources/views/emails/order_*.blade.php |
| Kundenkonto | resources/views/user/order/*.blade.php |
Verwendungszweck-Feld (KORRIGIERT):
| Falsch | Richtig |
|---|---|
shopping_payments.reference |
payment_transactions.txid |
Zugriff auf TXID:
// Model: App\Models\PaymentTransaction
$transaction = PaymentTransaction::where('shopping_payment_id', $payment->id)->first();
$verwendungszweck = $transaction->txid;
// Oder via transmitted_data JSON
$txid = $transaction->transmitted_data['txid'] ?? null;
Umsetzung:
- Checkout: Alert-Box mit Verwendungszweck-Hinweis bei Vorkasse-Auswahl
- E-Mail: Hervorgehobener Block mit Bankdaten + TXID als Verwendungszweck
- Kundenkonto: Info-Box bei unbezahlten Vorkasse-Bestellungen mit TXID
Beispiel-Text:
WICHTIG: Bitte geben Sie als Verwendungszweck ausschließlich folgende Nummer an:
[TXID: 123456789]
Nur so kann Ihre Zahlung automatisch zugeordnet werden.
[X] 4. Paketbox/Packstation Feld
Status: ✅ ERLEDIGT Priorität: Mittel Bereich: Adressverwaltung / Checkout
Anforderung:
- Neues Feld "DHL Postnummer" nur bei Lieferadresse
- Automatische Erkennung: Wenn Postnummer angegeben → Packstation-Modus
Technische Details:
| Parameter | Wert |
|---|---|
| Tabelle | shopping_users |
| Model | App\Models\ShoppingUser |
Durchgeführte Implementierung:
-
✅ Migration erstellt und ausgeführt
- Datei:
database/migrations/2026_01_22_181707_add_shipping_postnumber_to_shopping_users_table.php - Spalte:
shipping_postnumber VARCHAR(20) NULLABLE
- Datei:
-
✅ Model angepasst (
app/Models/ShoppingUser.php)- Feld im
$fillableArray - Methode
hasPostnumber()hinzugefügt
- Feld im
-
✅ Checkout-Formular (
resources/views/web/templates/checkout.blade.php)- Eingabefeld für Postnummer nach Telefon-Feld
- Placeholder: "12345678"
- JavaScript-Validierung für Packstation-Format
-
✅ DHL Modal (
resources/views/admin/dhl/modal_in_order_shipment.blade.php)- Postnummer-Feld hinzugefügt
-
✅ DHL Service (
app/Services/DhlModalService.php)- Postnummer wird korrekt an DHL API übergeben
-
✅ Kundendetail-Ansicht (
resources/views/admin/customer/_customer_detail.blade.php)- Postnummer wird mit Badge angezeigt
- Hinweistext für Packstation-Lieferung
-
✅ Kunden-Bearbeitungsformular (
resources/views/admin/customer/_edit.blade.php)- Postnummer-Feld direkt nach Telefon-Feld
- Mit Hinweistext und Placeholder
- Wird in User-Bereich und Admin-Bereich verwendet
-
✅ Lieferschein-PDF (
resources/views/pdf/delivery.blade.php)- Postnummer wird fett gedruckt in der Lieferadresse angezeigt
- Format: "DHL Postnummer: 12345678"
-
✅ Alert-Box im Formular (
resources/views/admin/customer/_edit.blade.php)- Deutliche gelbe Warning-Box erscheint, wenn Postnummer ausgefüllt wird
- Erklärt klar, dass bei Packstation-Lieferung:
- Straße/Nr. = "Packstation [Nummer]" (z.B. "Packstation 145")
- PLZ/Ort = Standort der Packstation (nicht Wohnadresse!)
- JavaScript-gesteuerte Ein-/Ausblendung bei Input
- Dismissable (kann vom User geschlossen werden)
-
✅ User-Account Formular (
resources/views/user/user_form.blade.php)
- Migration für
user_accountsTabelle erstellt und ausgeführt - Datei:
database/migrations/2026_01_23_102622_add_shipping_postnumber_to_user_accounts_table.php - Model
UserAccountim$fillableArray erweitert - Postnummer-Feld nach shipping_phone hinzugefügt
- Identische Alert-Box wie im Admin-Formular
- Identisches JavaScript für Ein-/Ausblendung
- ✅ Checkout-Formular (
resources/views/web/templates/checkout.blade.php)
- Alte kleine Info-Box (alert-info) ersetzt durch große Warning-Box
- Identische gelbe Alert-Box wie in allen anderen Formularen
- JavaScript bereits vorhanden (togglePackstationHint)
- Wird automatisch ein-/ausgeblendet bei Input
- ✅ CheckoutRepository Datenübertragung (
app/Repositories/CheckoutRepository.php)
- Problem behoben: Postnummer wurde nicht von UserAccount zu ShoppingUser übertragen
shoppingUserByAuthUser()erweitert (Zeile 350): Übertragung für eingeloggte UsershoppingUserAuthData()erweitert (Zeile 418 + 430): Übertragung für Salescenter-Bestellungen- Postnummer wird jetzt korrekt im Checkout-Formular angezeigt
- ✅ Anzeige-Views erweitert - Postnummer wird überall angezeigt:
- ✅
admin/sales/_detail.blade.php- Admin Bestelldetails - ✅
admin/sales/_detail_homparty_user.blade.php- Homeparty Bestelldetails - ✅
portal/order/_detail.blade.php- Portal Bestelldetails - ✅
emails/checkout_status.blade.php- Bestellstatus E-Mail - ✅
emails/checkout.blade.php- Checkout E-Mail (2 Stellen) - ✅
admin/modal/is_like_member.blade.php- Kundenzuordnung Modal (2 Stellen) - Format: Badge mit Icon + Hinweistext in Web-Views
- Format: Fett gedruckt "DHL Postnummer: XXX" in E-Mails
- ✅ Formular-Views erweitert - Postnummer-Eingabe überall möglich:
- ✅
portal/customer/_edit_form.blade.php- Portal Kundenformular- Postnummer-Feld nach shipping_phone
- Alert-Box mit JavaScript (togglePackstationAlert)
- Identisch zu anderen Formularen
- ✅
user/order/shipping_me.blade.php- Bestellung für mich selbst- Hidden field
shipping_postnumberhinzugefügt (2 Stellen) - Für
same_as_billingtrue/false Szenarien
- Hidden field
🔍 TIEFENPRÜFUNG DURCHGEFÜHRT - Weitere kritische Lücken gefunden und geschlossen!
- ✅ KRITISCHE CONTROLLER/SERVICES KORRIGIERT - Datenübertragung sichergestellt:
- ✅
app/Services/UserUtil.php(Zeile 101)- ShoppingUser-Erstellung aus UserAccount
shipping_postnumberfehlte komplett!
- ✅
app/Services/AboOrderCart.php(Zeilen 277 + 289)- Abo-Bestellungen: ShoppingUser aus UserAccount
shipping_postnumberfehlte an 2 Stellen (same_as_billing true/false)
- ✅
app/Services/PaymentHelper.php(Zeile 115)- Payment ShoppingUser Update
shipping_postnumberfehlte komplett!
- ✅ WEITERE FEHLENDE VIEWS ERGÄNZT:
- ✅
user/homeparty/_address.blade.php(2 Stellen)- Homeparty Adressanzeige (billing + shipping)
- Fett: "DHL Postnummer: XXX"
- ✅
user/order/payment/custom_payment.blade.php- Custom Payment Bestelldetails
- Badge mit Info-Text
- ✅
emails/custom_payment.blade.php- Custom Payment E-Mail
- Fett: "DHL Postnummer: XXX"
Übersetzungen:
- DE:
payment.dhl_postnumber= "DHL Postnummer" - EN:
payment.dhl_postnumber= "DHL Post Number" - ES:
payment.dhl_postnumber= "Número de correo DHL" - Neue Alert-Box Übersetzungen in
resources/lang/{de,en,es}/payment.php:packstation_alert_titlepackstation_alert_intropackstation_alert_streetpackstation_alert_street_examplepackstation_alert_locationpackstation_alert_not_homepackstation_alert_footer
DHL API Integration:
if ($user->shipping_postnumber) {
$recipient['postNumber'] = $user->shipping_postnumber;
// shipping_address enthält "Packstation 145" (3-stellige Nummer!)
}
Wichtige Hinweise zur Packstation-Nummer:
- ⚠️ Packstation-NUMMER ist 3-stellig (100-999, steht auf gelbem Schild)
- 📱 DHL Postnummer ist 6-10-stellig (separate Kundennummer in DHL App)
- 🚫 Häufiger Fehler: Postnummer wird als Packstation-Nummer eingegeben
- ✅ Richtig: "Packstation 145" (nicht "Packstation 12345")
- 📄 Anleitung:
/dev/22-01-2026/packstation-anleitung.md
Verbesserte Fehlermeldungen:
- Detaillierte Fehlermeldung bei ungültiger Packstation-Nummer
- Frontend-Hinweise in allen Formularen aktualisiert (DE, EN, ES)
- Logging mit allen relevanten Daten für besseres Debugging
[X] 5. Set/Kit Produkte: Inhalte auflisten
Priorität: Mittel Bereich: Produkte / Rechnungen / Lieferscheine
Problem: Bei Sets/Kits werden enthaltene Einzelprodukte nicht aufgelistet.
Anforderung:
- Alle enthaltenen Produkte unter dem Set auflisten
- Auf Rechnung und Lieferschein ausweisen
- Admin-UI: Dropdown + Liste (wie bei Inhaltsstoffen)
Referenz-Implementierung (Inhaltsstoffe):
| Parameter | Wert |
|---|---|
| Pivot-Tabelle | product_ingredients |
| Model | App\Models\ProductIngredient |
| Relation | Product::p_ingredients() (belongsToMany) |
Neue Tabellen-Struktur (analog zu Inhaltsstoffen):
CREATE TABLE product_bundles (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT UNSIGNED NOT NULL, -- Das Set/Kit (Parent)
bundle_product_id BIGINT UNSIGNED NOT NULL, -- Enthaltenes Produkt (Child)
quantity INT UNSIGNED DEFAULT 1,
pos INT UNSIGNED DEFAULT 0, -- Sortierung
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
FOREIGN KEY (bundle_product_id) REFERENCES products(id) ON DELETE CASCADE,
UNIQUE KEY unique_bundle (product_id, bundle_product_id)
);
Neue Model-Relation in Product.php:
public function bundleItems()
{
return $this->belongsToMany(Product::class, 'product_bundles', 'product_id', 'bundle_product_id')
->withPivot('quantity', 'pos')
->orderBy('pos');
}
public function isBundle(): bool
{
return $this->bundleItems()->count() > 0;
}
Admin-UI (wie Inhaltsstoffe):
- Dropdown zur Produktauswahl
- Listenansicht mit Menge und Sortierung
- Vorlage:
resources/views/admin/product/→ Ingredients-Sektion kopieren
Betroffene Views:
resources/views/pdf/invoice.blade.php→ Bundle-Items unter Produkt auflistenresources/views/pdf/delivery.blade.php→ Bundle-Items auflisten- Shop-Produktdetailseiten
[!] 6. Mehrsprachigkeit: Rechnungen, Provisionen, Lieferscheine
Priorität: Hoch Bereich: PDF-Generierung / E-Mail
Anforderung:
- Deutsche Version bleibt primär (rechtlich bindend)
- Zusätzliche Kopie in Landessprache (EN, ES, FR)
- Sprache aus User-Einstellung (
users.locale)
Technische Details:
| Parameter | Wert |
|---|---|
| PDF-Service | App\Services\Invoice |
| Templates | resources/views/pdf/*.blade.php |
App\Mail\MailInvoice |
|
| User-Sprache | users.locale (Spalte prüfen/anlegen) |
Betroffene Dokumente:
| Dokument | Datei | Status |
|---|---|---|
| Rechnung | invoice.blade.php |
[ ] |
| Lieferschein | delivery.blade.php |
[ ] |
| Provisionsabrechnung | credit_details.blade.php |
[ ] |
| Stornorechnung | (neu) | [ ] |
| Mitgliedschaftsverlängerung | E-Mail Template | [ ] |
| Partnerantrag/Vertrag | PDF Template | [ ] |
Umsetzung:
// Invoice Service erweitern
public function generatePdf(Order $order, string $locale = 'de'): string
{
app()->setLocale($locale);
// PDF generieren...
}
// Zwei PDFs generieren
$pdfDE = $this->generatePdf($order, 'de');
$pdfUser = $this->generatePdf($order, $user->locale ?? 'de');
// Bei E-Mail beide anhängen (wenn unterschiedlich)
if ($user->locale && $user->locale !== 'de') {
$mail->attach($pdfDE, ['as' => 'Rechnung-DE.pdf']);
$mail->attach($pdfUser, ['as' => 'Invoice-' . strtoupper($user->locale) . '.pdf']);
}
Speicherung:
- Beide PDFs im System speichern (Bestellungen-Ansicht)
- Zusätzliche Spalten in
user_invoices:file_localized,locale
✅ 7. Stornorechnungen mit Punktekorrektur [ERLEDIGT 06.02.2026]
Priorität: Hoch Bereich: Admin / Rechnungswesen / Marketingplan
Problem:
- Storno-Button fehlt im Admin
- Punkte werden bei Storno NICHT abgezogen (in gesamter MLM-Struktur)
Anforderung:
- Button "Stornorechnung erstellen" neben Rechnung
- Negativbetrag im Rechnungskreis
- Punkte in gesamter MLM-Struktur korrigieren (Upline!)
- Mehrsprachigkeit beachten
Technische Details:
| Parameter | Wert |
|---|---|
| Model | App\Models\UserInvoice |
| Storno-Felder | cancellation, cancellation_id, cancellation_date |
| Punkte-Model | App\Models\UserSalesVolume |
| Business-Model | App\Models\UserBusiness |
| Admin-View | resources/views/admin/order/*.blade.php |
Punktekorrektur-Logik:
// 1. Original-SalesVolume finden
$salesVolume = UserSalesVolume::where('order_id', $order->id)->first();
// 2. Punkte negieren (neuer Eintrag mit negativen Werten)
UserSalesVolume::create([
'user_id' => $salesVolume->user_id,
'order_id' => $order->id,
'month' => $salesVolume->month,
'year' => $salesVolume->year,
'month_points' => -$salesVolume->month_points,
'month_KP_points' => -$salesVolume->month_KP_points,
'status' => 6, // Neuer Status: 'cancelled'
]);
// 3. Upline-Struktur durchlaufen und korrigieren
// → TreeCalcBotOptimized neu berechnen oder separater CancellationService
Betroffene Tabellen:
user_sales_volumes → Negativer Eintrag hinzufügen
user_business → Monatsdaten neu berechnen (oder Neuberechnung triggern)
Admin-Route:
Route::post('/admin/invoice/{id}/cancel', [InvoiceController::class, 'cancel']);
✅ UMSETZUNG ABGESCHLOSSEN (06.02.2026)
Status: ✅ ERLEDIGT
Die Stornorechnung-Funktionalität mit automatischer Punktekorrektur wurde vollständig implementiert und ist produktionsbereit.
Implementierte Komponenten
1. Backend-Logik (app/Repositories/InvoiceRepository.php)
Neue Methode createCancellation():
- Erstellt neue Stornorechnung mit eindeutiger Rechnungsnummer
- Generiert PDF-Dokumente (Rechnung + Lieferschein) mit negativen Werten
- Markiert Originalrechnung als storniert (
cancellation=true,cancellation_id,cancellation_date) - Setzt Bestellstatus:
txaction = 'cancelled'(Zahlungsstatus)shipped = 10(Versandstatus), nur wenn noch nicht versendet (shipped< 2)
- Ruft Punktekorrektur-Service auf
- Optional: E-Mail-Versand der Stornorechnung
- Vollständige DB-Transaktion mit Rollback bei Fehlern
2. Punktekorrektur-Service (app/Services/BusinessPlan/SalesPointsVolume.php)
Neue Methode cancelSalesPointsVolume():
- Erstellt
UserSalesVolume-Eintrag mit negativen Punkten und Beträgen - Status 6 =
cancelled(neu hinzugefügt) - Triggert automatische Neuberechnung der MLM-Struktur via
reCalculateSalesPointsVolume() - Korrigiert Upline-Punkte und monatliche Summen (KP, TP, Shop-Punkte)
- Vollständiges Logging für Nachvollziehbarkeit
Erweiterte Methode reCalculateSalesPointsVolume():
- Case 6 hinzugefügt für Storno-Einträge
- Negative Werte werden korrekt in monatliche Summen eingerechnet
- Unterscheidet zwischen Shop- und Berater-Punkten (
status_turnover)
3. Controller (app/Http/Controllers/SalesController.php)
Neue Methode invoiceCancellation():
- Validiert Anfrage und prüft Berechtigungen
- Prüft, ob Originalrechnung existiert
- Prüft, ob bereits storniert wurde (verhindert Doppel-Storno)
- Ruft Repository-Methode auf
- Fehlerbehandlung mit Flash-Messages
- Redirect zurück zur Bestelldetailseite
4. Model-Relationen (app/Models/ShoppingOrder.php)
Überarbeitete Eloquent-Relations:
user_invoice(): Findet Original-Rechnung korrekt (auch nach Storno)- Logik:
cancellation=falseODERcancellation_id IS NOT NULL
- Logik:
user_cancellation_invoice(): Findet Stornorechnung- Logik:
cancellation=trueUNDcancellation_id IS NULL
- Logik:
user_invoices(): Alle Rechnungen (Original + Storno)
Neue Hilfsmethoden:
isCancellationInvoice(): Prüft, ob Stornorechnung existiertgetCancellationInvoice(): Holt Stornorechnung-Entity
5. File-Controller (app/Http/Controllers/FileController.php)
Erweitert für $from === 'cancellation':
- Lädt korrekt die Stornorechnung-PDF (nicht die Originalrechnung)
- Query mit
whereNull('cancellation_id')für eindeutige Identifikation - Unterstützt mehrsprachige PDF-Versionen (DE, EN, ES)
- Respektiert Berechtigungen und Disk-Konfiguration
6. PDF-Templates (neu erstellt)
-
resources/views/pdf/cancellation.blade.php- Rotes Design mit "STORNORECHNUNG" Header
- Referenz zur Originalrechnung
- Negatives Vorzeichen bei allen Beträgen
-
resources/views/pdf/cancellation-detail.blade.php- Negativer Einzelpreis und Gesamtpreis
- Kompatibel mit
ShoppingOrderItem-Struktur
-
resources/views/pdf/cancellation_delivery.blade.php- Lieferschein für Stornolieferung
- Negative Mengen
-
resources/views/pdf/cancellation-detail-homeparty.blade.php- Spezialversion für Homeparty-Bestellungen
- Negative Punkte und Beträge
-
resources/views/pdf/cancellation-detail-collection.blade.php- Spezialversion für Sammelbestellungen
- Negative Summen
7. Admin-UI (resources/views/admin/sales/_detail.blade.php)
Stornorechnung-Sektion:
- Zeigt Badge mit Rechnungsnummer (rot)
- Download/Stream-Buttons für Stornorechnung (rot)
- Mehrsprachige PDF-Versionen (DE, EN, ES)
- Button "Stornorechnung erstellen" (nur wenn noch nicht storniert)
- Datum der Storno-Erstellung
- Originalrechnung bleibt sichtbar nach Storno
Modal für Stornorechnung:
resources/views/admin/sales/_detail.blade.php(Zeilen 781-836)- Datums-Auswahl für Stornodatum
- Checkbox für E-Mail-Versand
- Bestätigungs-Button
8. Business Points Admin
DataTable (app/Http/Controllers/BusinessPointsController.php):
- Zeigt Storno-Einträge mit rotem Undo-Icon
- Link zur betroffenen Bestellung
Detail-Modal (resources/views/admin/business/modal_edit_points.blade.php):
- Zeigt Stornorechnung mit rotem Badge
- Link zu Stornorechnung-PDF (primär)
- Link zu Originalrechnung (sekundär, klein)
- Negative Werte werden rot dargestellt
- Zeigt monatliche Summen und Syslog
9. Status-System
Neue Status-Werte:
UserSalesVolume::$statusTypes[6] = 'cancelled'UserSalesVolume::$statusColors[6] = 'danger'(rot)Payment::$txaction_text['cancelled'] = 'cancelled'Payment::$txaction_color['cancelled'] = 'danger'(rot)
10. Übersetzungen
Ergänzt in resources/lang/{de,en,es}/pdf.php:
cancellation_invoice: "Stornorechnung" / "Cancellation Invoice" / "Factura de cancelación"cancellation_nr: "Storno-Nr." / "Cancellation No." / "No. de cancelación"cancellation_for: "Storno für Rechnung" / "Cancellation for Invoice" / "Cancelación de factura"cancelled: "Storniert" / "Cancelled" / "Cancelado"cancellation_delivery: "Storno-Lieferschein" / "Cancellation Delivery Note" / "Albarán de cancelación"
Status in resources/lang/{de,en,es}/payment.php:
cancelled: "Storniert" / "Cancelled" / "Cancelado" (bereits vorhanden)
Routing
CRM Domain (routes/domains/crm.php Zeile 346-347):
Route::post('/admin/sales/invoice/cancellation',
'SalesController@invoiceCancellation')
->name('admin_sales_invoice_cancellation');
Datenbank-Struktur
Bestehende Felder genutzt:
user_invoices:
cancellation(boolean):truebei Original UND Stornorechnungcancellation_id(int): Nur bei Originalrechnung (zeigt auf Storno),NULLbei Stornorechnungcancellation_date(string): Stornodatum
Unterscheidung:
- Originalrechnung (nach Storno):
cancellation=true,cancellation_id IS NOT NULL - Stornorechnung:
cancellation=true,cancellation_id IS NULL
user_sales_volumes:
status = 6für Storno-Einträge- Negative
pointsundtotal_net
shopping_orders:
txaction = 'cancelled'(Zahlungsstatus)shipped = 10(Versandstatus "storniert")
Workflow
- Admin klickt "Stornorechnung erstellen"
- Modal öffnet sich → Datum auswählen, optional E-Mail aktivieren
- POST an
admin_sales_invoice_cancellation - Controller validiert → keine Doppel-Stornos
- Repository erstellt Storno:
- Neue Rechnungsnummer
- PDF-Generierung (Rechnung + Lieferschein)
- DB-Eintrag für Stornorechnung
- Originalrechnung markieren
- Bestellstatus aktualisieren
- Service korrigiert Punkte:
- Neuer
UserSalesVolume-Eintrag (negativ) - MLM-Struktur neu berechnen
- Monatssummen aktualisieren
- Neuer
- Redirect mit Success-Message
- UI zeigt:
- Original- UND Stornorechnung
- Roter Status "Storniert"
- Punktekorrektur in Business-Points-Liste
Besondere Features
- ✅ Atomare Transaktion: Alles oder nichts (DB-Rollback bei Fehler)
- ✅ Mehrsprachigkeit: DE, EN, ES für alle PDFs
- ✅ Upline-Korrektur: Komplette MLM-Hierarchie wird neu berechnet
- ✅ Verhindert Doppel-Storno: Check auf bestehende Stornorechnung
- ✅ Versand-Logik: Nur offene Bestellungen werden versandstorniert
- ✅ Logging: Vollständige Nachvollziehbarkeit via
syslogund\Log - ✅ Performance: Memory-optimiert, nutzt
TreeCalcBotOptimized - ✅ UI-Konsistenz: Roter Farbcode durchgängig (danger)
Testing-Checkliste
- Stornorechnung erstellen für Berater-Bestellung
- Stornorechnung erstellen für Shop-Bestellung
- Stornorechnung erstellen für Homeparty
- Stornorechnung erstellen für Sammelbestellung
- PDF-Download DE, EN, ES
- Punktekorrektur in
user_sales_volumesprüfen - Monatssummen in Business Points prüfen
- Upline-Punkte prüfen (MLM-Hierarchie)
- Bestellstatus "Storniert" anzeigen
- Versandstatus "Storniert" (nur bei offenen)
- Doppel-Storno verhindert
- E-Mail-Versand optional
Performance
- Memory: Effizient durch Service-basierte Architektur
- DB-Queries: Optimiert mit Eloquent Relations
- PDF-Generation: Nutzt bestehende
InvoicePDF-Klasse - Recalculation: Nutzt optimierte
TreeCalcBotOptimized
Geänderte Dateien
Backend:
app/Http/Controllers/SalesController.php(MethodeinvoiceCancellation())app/Http/Controllers/FileController.php(Stornorechnung laden)app/Repositories/InvoiceRepository.php(MethodecreateCancellation())app/Services/BusinessPlan/SalesPointsVolume.php(Status 6,cancelSalesPointsVolume())app/Services/Invoice.php(Hilfsmethoden für Dateinamen)app/Services/Payment.php(txaction 'cancelled')app/Models/ShoppingOrder.php(Relations überarbeitet)app/Models/UserSalesVolume.php(Status 6 hinzugefügt)
Frontend:
resources/views/admin/sales/_detail.blade.php(Storno-Sektion + Modal)resources/views/admin/business/points.blade.php(DataTable)resources/views/admin/business/modal_edit_points.blade.php(Detail-Modal)
PDF-Templates (neu):
resources/views/pdf/cancellation.blade.phpresources/views/pdf/cancellation-detail.blade.phpresources/views/pdf/cancellation_delivery.blade.phpresources/views/pdf/cancellation-detail-homeparty.blade.phpresources/views/pdf/cancellation-detail-collection.blade.php
Übersetzungen:
resources/lang/de/pdf.phpresources/lang/en/pdf.phpresources/lang/es/pdf.phpresources/lang/de/payment.php(bereits vorhanden)resources/lang/en/payment.php(bereits vorhanden)resources/lang/es/payment.php(bereits vorhanden)
Routing:
routes/domains/crm.php(Route bereits vorhanden)
Technische Highlights
- Saubere Architektur: Controller → Repository → Service → Model
- Eloquent Relations: Komplexe WHERE-Queries in Relations gekapselt
- DB-Transaktionen: Atomare Operationen mit Rollback
- Service-Layer: Business-Logik getrennt von Data-Access
- Code-Qualität: Pint-formatiert, PSR-12 konform
- Fehlerbehandlung: Try-Catch mit aussagekräftigen Fehlermeldungen
- Logging: Strukturiertes Logging mit Kontext-Daten
Dokumentation
- Diese Datei:
dev/22-01-2026/next-steps.md - Code-Kommentare in allen geänderten Dateien
- Log-Messages für Debugging und Monitoring
Abgeschlossen am: 06.02.2026
[ ] 8. Französisch hinzufügen
Priorität: Mittel Bereich: Lokalisierung
Anforderung:
- Neue Sprachdateien:
resources/lang/fr/ - Monatsstatistik übersetzen
- Vorkasse-Texte übersetzen
Technische Details:
| Parameter | Wert |
|---|---|
| Verzeichnis | resources/lang/fr/ |
| Vorlage | resources/lang/de/ kopieren |
Dateien erstellen:
resources/lang/fr/
├── abo.php
├── backend.php
├── cal.php
├── customer.php
├── email.php
├── home.php
├── marketingplan.php
├── navigation.php
├── payment.php
└── team.php
Umsetzung:
cp -r resources/lang/de resources/lang/fr
# Dann alle Dateien übersetzen
[!] 9. Gutschriften: Falsche Punkteberechnung
Priorität: Hoch Bereich: Marketingplan / Team-Ansicht
Problem: Gutschriften werden nicht korrekt zu Punkten addiert. Unterschiedliche Anzeige für Admin vs. User.
Beispiel:
- Monika Kunz: Admin sieht 625 Punkte, User sieht 1115 Punkte (Dezember)
- Differenz: 490 Punkte → vermutlich Gutschrift nicht berücksichtigt
Technische Details:
| Parameter | Wert |
|---|---|
| Service | App\Services\BusinessPlan\TreeCalcBotOptimized |
| Status | UserSalesVolume.status = 4 (credit/Gutschrift) |
| Model | App\Models\UserSalesVolume |
Status-Mapping:
0 => 'not_assigned'
1 => 'advisor_order'
2 => 'shoporder'
3 => 'shoporder_pending'
4 => 'credit' // ← Gutschrift
5 => 'registration'
Debugging-Schritte:
- Query für User mit
status = 4im betroffenen Monat prüfen:UserSalesVolume::where('user_id', $userId) ->where('month', 12)->where('year', 2025) ->where('status', 4)->get(); - Berechnung in
getPointsKPSum()/getPointsTPSum()validieren - Team-View Query vs. Admin-View Query vergleichen
- Prüfen ob
status_pointskorrekt gesetzt ist
Beispiel-Fall:
- App\Models\UserSalesVolume user_id=1218 year=2025 month=12 -> 32758 ist eine Gutschrift und wird hier richtig summiert, in der Auswertung steht allerdings 545 month_KP_points also ohne die Gutschrift.
- Die Auswertung erfolgt live über App\Services\BusinessPlan\TreeCalcBotOptimized und wird nach abgeschlossenem Monat über einen Job mit diesem Controller app/Console/Commands/BusinessStoreOptimized.php in der DB app/Models/UserBusiness.php gespeichert
- Die Zusammenfassung für das Beispiel ist hier UserBusiness ID=21264 - es sind klar die falschen Werte zusammengerechnet worden
🔍 TECHNISCHE ANALYSE - Gutschriften-System (28.01.2026)
System-Architektur
1. Wo werden Gutschriften erstellt?
| Stelle | Datei | Funktion | Typ |
|---|---|---|---|
| Manuell Admin | app/Services/BusinessPlan/SalesPointsVolume.php |
addSalesPointsVolume() |
Punkte-Gutschrift (status=4) |
| Automatisch Provisionen | app/Cron/UserPaymentCredits.php |
addUserCreditItem() |
Zahlungs-Gutschrift (UserCreditItem) |
| PDF-Gutschriften | app/Repositories/CreditRepository.php |
create() |
Zahlungs-PDFs |
2. Datenfluss Punkte-Berechnung
UserSalesVolume (Tabelle)
├─ status = 1 (advisor_order) → Eigene Bestellung
├─ status = 2 (shoporder) → Shop-Bestellung
├─ status = 3 (shoporder_pending) → Shop ausstehend
├─ status = 4 (credit) → 🔥 GUTSCHRIFT
└─ status = 5 (registration) → Registrierung
│
↓ Bei Änderung/Erstellung
│
SalesPointsVolume::reCalculateSalesPointsVolume()
├─ Durchläuft ALLE Einträge eines Users (month/year)
├─ Sortierung: orderBy('id', 'ASC') ✅ Chronologisch
├─ Berechnet kumulative Summe: month_KP_points, month_TP_points
├─ Bei status=4: add_KP_TP_Points() wird aufgerufen
└─ Speichert Werte in JEDEM Eintrag
│
↓ Letzter Eintrag enthält Gesamtsumme
│
User::getUserSalesVolume($month, $year, 'first')
├─ Query: orderBy('id', 'DESC') → Gibt LETZTEN Eintrag
└─ Gibt UserSalesVolume-Objekt zurück
│
↓
User::getUserSalesVolumeBy($month, $year, $field)
├─ Ruft getUserSalesVolume('first') auf
└─ Bei 'sales_volume_points_KP_sum': getPointsKPSum()
│
↓
UserSalesVolume::getPointsKPSum()
└─ return month_KP_points + month_shop_points
│
↓ Live-Berechnung
│
BusinessUserItemOptimized::getUserSalesVolumeOptimized()
└─ Ruft User::getUserSalesVolumeBy() auf
│
↓ Speicherung nach Monatsende
│
BusinessStoreOptimized → UserBusiness (Tabelle)
🔴 Identifizierte Fehlerquellen
HAUPTPROBLEM 1: Fehlende Neuberechnung
// SalesPointsVolume::addSalesPointsVolume() - Zeile 219-257
// Wenn eine Gutschrift manuell hinzugefügt wird:
$user_sales_volume = UserSalesVolume::create([
'status' => 4, // Gutschrift
'status_points' => $data['status_points'], // ⚠️ Muss gesetzt sein!
// ...
]);
// ✅ Neuberechnung wird aufgerufen:
self::reCalculateSalesPointsVolume($user_sales_volume->user_id, ...);
Aber: Wenn Gutschriften direkt über DB oder andere Wege erstellt werden, fehlt dieser Aufruf!
HAUPTPROBLEM 2: status_points nicht gesetzt
In SalesPointsVolume::reCalculateSalesPointsVolume() - Zeile 54-64:
private static function add_KP_TP_Points($userSalesVolume, $month_points)
{
if ($userSalesVolume->status_points === 2) { // NUR KP
$month_points->KP += $userSalesVolume->points;
} else {
// === 1 oder NULL/0 //TP + KP
$month_points->KP += $userSalesVolume->points;
$month_points->TP += $userSalesVolume->points;
}
return $month_points;
}
Problem: Wenn status_points nicht gesetzt ist (NULL/0), wird es als KP + TP behandelt.
Bei Gutschriften ist das korrekt, ABER nur wenn die Gutschrift auch durchlaufen wird!
HAUPTPROBLEM 3: Timing-Problem
Szenario:
- Monat 12/2025 läuft
- User 1218 hat mehrere UserSalesVolume-Einträge
reCalculateSalesPointsVolume()wird ausgeführt → letzter Eintrag hat z.B. 545 Punkte- NACH der letzten Berechnung wird eine Gutschrift (32.758 Punkte) hinzugefügt
- ABER:
reCalculateSalesPointsVolume()wird NICHT erneut aufgerufen - ❌ Die 32.758 Punkte fehlen in der Auswertung
Lösung: Bei jedem Hinzufügen von Gutschriften MUSS reCalculateSalesPointsVolume() aufgerufen werden.
HAUPTPROBLEM 4: Reihenfolge bei mehreren Einträgen
// User::getUserSalesVolume() - Zeile 609-622
$query = UserSalesVolume::with($relations)
->where('user_id', $this->id)
->where('month', $month)
->where('year', $year)
->orderBy('id', 'DESC'); // ⚠️ Gibt LETZTEN Eintrag zurück
switch ($record) {
case 'first':
return $query->first(); // Letzter Eintrag mit höchster ID
}
Annahme: Der letzte Eintrag (höchste ID) enthält die kumulierte Summe ALLER Punkte. Problem: Wenn nach dem letzten Eintrag keine Neuberechnung erfolgt, ist diese Annahme falsch!
HAUPTPROBLEM 5: Status-Filter (KEIN Problem)
In der Berechnung gibt es KEINEN expliziten Filter für status = 4 → Gutschriften werden inkludiert.
// SalesPointsVolume::reCalculateSalesPointsVolume() - Zeile 70
$userSalesVolumes = UserSalesVolume::where('user_id', $user_id)
->where('month', $month)
->where('year', $year)
->orderBy('id', 'ASC') // ✅ Chronologisch
->get(); // ✅ ALLE Einträge (inkl. status=4)
foreach ($userSalesVolumes as $userSalesVolume) {
switch ($userSalesVolume->status) {
case 1: // Bestellung
case 4: // Gutschrift ✅ WIRD VERARBEITET
case 5: // Registrierung
$month_points = self::add_KP_TP_Points($userSalesVolume, $month_points);
}
}
Fazit: Gutschriften werden in der Berechnung korrekt verarbeitet! ✅
🧪 Prüfschritte für user_id=1218, month=12, year=2025
1. Alle UserSalesVolume-Einträge prüfen:
SELECT id, status, status_points, points, month_KP_points, month_TP_points,
month_shop_points, created_at, updated_at, message
FROM user_sales_volumes
WHERE user_id = 1218 AND month = 12 AND year = 2025
ORDER BY id ASC;
Zu prüfen:
- ✅ Wie viele Einträge gibt es?
- ✅ Welche ID hat die Gutschrift (32.758 Punkte, status=4)?
- ✅ Hat sie
status_pointsgesetzt? (sollte 1 oder 2 sein) - ✅ Ist die Gutschrift der letzte Eintrag (höchste ID)?
- ✅ Wenn nicht: Wurde nach der Gutschrift
reCalculateSalesPointsVolume()aufgerufen? - ✅ Hat der LETZTE Eintrag (höchste ID) die korrekten
month_KP_points?
2. UserBusiness-Eintrag prüfen:
SELECT id, user_id, month, year,
sales_volume_KP_points, sales_volume_TP_points,
sales_volume_points_KP_sum, sales_volume_points_TP_sum,
created_at, updated_at
FROM user_businesses
WHERE id = 21264;
Zu prüfen:
- ✅ Welches Datum hat
updated_at? - ✅ Wurde der Eintrag VOR oder NACH der Gutschrift erstellt?
- ✅ Passt
sales_volume_points_KP_sumzu den Punkten im letzten UserSalesVolume-Eintrag?
3. Live-Berechnung testen:
// Im Controller oder Tinker:
$user = User::find(1218);
$value = $user->getUserSalesVolumeBy(12, 2025, 'sales_volume_points_KP_sum');
echo "KP Sum: " . $value . "\n";
// Detailliert:
$volumes = UserSalesVolume::where('user_id', 1218)
->where('month', 12)
->where('year', 2025)
->orderBy('id', 'ASC')
->get();
foreach ($volumes as $v) {
echo "ID: {$v->id} | Status: {$v->status} | Points: {$v->points} | month_KP: {$v->month_KP_points}\n";
}
// Letzter Eintrag:
$last = $volumes->last();
echo "Letzter Eintrag - month_KP_points: " . $last->month_KP_points . "\n";
echo " + month_shop_points: " . $last->month_shop_points . "\n";
echo " = " . $last->getPointsKPSum() . "\n";
💡 Lösungsansätze
✅ Lösung 1: Neuberechnung erzwingen
Nach jeder Änderung an UserSalesVolume MUSS die Neuberechnung erfolgen:
// Überall wo UserSalesVolume erstellt/geändert wird:
use App\Services\BusinessPlan\SalesPointsVolume;
// Nach create/update:
SalesPointsVolume::reCalculateSalesPointsVolume($user_id, $month, $year);
Stellen prüfen:
- Manuelles Hinzufügen (Admin)
- Import-Skripte
- API-Endpunkte
- Migrations/Seeders
✅ Lösung 2: Model Event Hook (EMPFOHLEN)
Automatische Neuberechnung bei Änderungen:
// In App\Models\UserSalesVolume
protected static function booted()
{
// Nach Create
static::created(function ($userSalesVolume) {
if ($userSalesVolume->user_id && $userSalesVolume->isCurrentMonthYear()) {
\App\Services\BusinessPlan\SalesPointsVolume::reCalculateSalesPointsVolume(
$userSalesVolume->user_id,
$userSalesVolume->month,
$userSalesVolume->year
);
}
});
// Nach Update
static::updated(function ($userSalesVolume) {
if ($userSalesVolume->user_id && $userSalesVolume->isCurrentMonthYear()) {
\App\Services\BusinessPlan\SalesPointsVolume::reCalculateSalesPointsVolume(
$userSalesVolume->user_id,
$userSalesVolume->month,
$userSalesVolume->year
);
}
});
}
⚠️ Achtung: Kann zu Performance-Problemen führen, wenn viele Einträge gleichzeitig aktualisiert werden.
✅ Lösung 3: Validation vor Speicherung
Sicherstellen, dass status_points immer gesetzt ist:
// In App\Models\UserSalesVolume
protected static function booted()
{
static::creating(function ($userSalesVolume) {
// Fallback: Wenn status_points nicht gesetzt, Standard = 1 (KP+TP)
if (!$userSalesVolume->status_points) {
$userSalesVolume->status_points = 1;
}
});
}
✅ Lösung 4: Admin-Prüfung & Warnung
Dashboard-Widget für inkonsistente Daten:
// Query für Einträge ohne korrekte Neuberechnung:
$inconsistent = DB::select("
SELECT usv1.user_id, usv1.month, usv1.year,
MAX(usv1.id) as last_id,
(SELECT month_KP_points FROM user_sales_volumes usv2
WHERE usv2.id = MAX(usv1.id)) as last_month_KP,
SUM(CASE WHEN usv1.status IN (1,4,5) THEN usv1.points ELSE 0 END) as expected_KP
FROM user_sales_volumes usv1
WHERE usv1.month = ? AND usv1.year = ?
GROUP BY usv1.user_id, usv1.month, usv1.year
HAVING last_month_KP != expected_KP
", [12, 2025]);
📋 Empfohlene Maßnahmen
| Priorität | Maßnahme | Aufwand | Risiko |
|---|---|---|---|
| 🔴 Hoch | Daten für user_id=1218 prüfen (Prüfschritte 1-3) | 30 min | Niedrig |
| 🔴 Hoch | Neuberechnung manuell anstoßen für betroffene User | 1h | Niedrig |
| 🟡 Mittel | Model Event Hook implementieren (Lösung 2) | 2h | Mittel (Performance) |
| 🟢 Niedrig | Validation hinzufügen (Lösung 3) | 1h | Niedrig |
| 🟢 Niedrig | Admin-Prüfung implementieren (Lösung 4) | 3h | Niedrig |
Nächste Schritte:
- ✅ Technische Analyse abgeschlossen (28.01.2026)
- ✅ Datenbank-Analyse durchgeführt (28.01.2026)
- ✅ Ursache identifiziert (siehe unten)
- ⏭️ Lösung implementieren
- ⏭️ Betroffene User identifizieren und korrigieren
🎯 URSACHE IDENTIFIZIERT (28.01.2026)
⚠️ WICHTIG: Diese Analyse wurde auf dem Test-Server durchgeführt. Das Problem existiert auch auf dem Live-Server (Hetzner/Ploi.io)!
Server-Konfiguration
Test-Server: Lokale Development-Umgebung (Laravel Sail)
Live-Server:
- Hoster: Hetzner (Deutschland)
- Verwaltung: Ploi.io
- Cron-Job:
cd /home/ploi/mivita.care && php8.4 artisan schedule:run >> /dev/null 2>&1(läuft jede Minute) - Scheduler:
business:store-optimized 0 0läuft täglich um 03:00 Uhr
Konkrete Analyse für user_id=1218, month=12, year=2025
UserSalesVolume-Einträge (4 Stück):
| ID | Status | Typ | Punkte | month_KP | month_shop | Datum |
|---|---|---|---|---|---|---|
| 32171 | 1 | advisor_order | 52 | 52 | 0 | 16.12.2025 07:39 |
| 32377 | 1 | advisor_order | 493 | 545 | 0 | 18.12.2025 22:45 |
| 32725 | 2 | shoporder | 80 | 545 | 80 | 30.12.2025 14:12 |
| 32758 | 4 | credit | 490 | 1035 | 80 | 31.12.2025 17:10 ⚠️ |
Letzter Eintrag (ID 32758) - KORREKT:
- month_KP_points: 1035 ✅
- month_shop_points: 80 ✅
- getPointsKPSum(): 1115 ✅
UserBusiness (ID 21264) - VERALTET:
- sales_volume_KP_points: 545 ❌ (fehlen 490 Punkte!)
- sales_volume_points_shop: 80 ✅
- sales_volume_points_KP_sum: 625 ❌ (sollte 1115 sein!)
- Erstellt/Updated: 31.12.2025 03:00:28 ⚠️
Live-Berechnung (TreeCalcBot) - KORREKT:
- getUserSalesVolumeBy('sales_volume_points_KP_sum'): 1115 ✅
- getUserSalesVolumeBy('sales_volume_KP_points'): 1035 ✅
- getUserSalesVolumeBy('sales_volume_points_shop'): 80 ✅
🔴 HAUPTURSACHE: UserBusiness wird nach Monatsende nicht aktualisiert
Timeline 31.12.2025:
03:00:28 → BusinessStoreOptimized läuft (Cron-Job)
├─ Liest UserSalesVolume (nur 3 Einträge vorhanden)
├─ Berechnet: 545 KP + 80 Shop = 625 Gesamt
└─ Speichert UserBusiness mit 625 Punkten ✅
17:10:05 → Gutschrift wird manuell hinzugefügt (490 Punkte)
├─ UserSalesVolume wird erstellt (ID 32758) ✅
├─ reCalculateSalesPointsVolume() läuft ✅
├─ Alle UserSalesVolume-Einträge werden aktualisiert ✅
│ └─ Letzter Eintrag hat jetzt: 1035 KP + 80 Shop = 1115 ✅
└─ UserBusiness wird NICHT aktualisiert! ❌
└─ Bleibt bei 625 Punkten (veraltet)
Das Problem:
- UserSalesVolume ist korrekt (1115 Punkte) ✅
- Live-Berechnung ist korrekt (1115 Punkte) ✅
- Gespeicherte UserBusiness ist veraltet (625 Punkte) ❌
Auswirkung:
- Team-Ansichten, die auf UserBusiness basieren, zeigen falsche Werte
- Provisionsberechnungen könnten betroffen sein
- Reports zeigen inkorrekte Daten für abgeschlossene Monate
💡 LÖSUNGEN
🔴 Sofortmaßnahme: Manuelle Korrektur
# UserBusiness für betroffenen Monat neu berechnen
php artisan business:store-optimized 12 2025 --clear
Oder nur für User 1218:
// In Tinker oder als Command
$user = User::find(1218);
$month = 12;
$year = 2025;
// BusinessUserItem erstellen mit Live-Daten
$treeCalc = new \App\Services\BusinessPlan\TreeCalcBotOptimized($month, $year, 'admin', true);
$treeCalc->initStructureUser($user->id, true);
$businessUserItem = $treeCalc->getBusinessUser($user->id);
// UserBusiness aktualisieren
$userBusiness = \App\Models\UserBusiness::where('user_id', 1218)
->where('month', 12)
->where('year', 2025)
->first();
if ($userBusiness && $businessUserItem) {
$userBusiness->sales_volume_KP_points = $businessUserItem->b_user->sales_volume_KP_points;
$userBusiness->sales_volume_TP_points = $businessUserItem->b_user->sales_volume_TP_points;
$userBusiness->sales_volume_points_KP_sum = $businessUserItem->b_user->sales_volume_points_KP_sum;
$userBusiness->sales_volume_points_TP_sum = $businessUserItem->b_user->sales_volume_points_TP_sum;
$userBusiness->save();
echo "UserBusiness updated!\n";
}
🟡 Mittelfristige Lösung: Automatische Aktualisierung
Option A: Event Hook in UserSalesVolume
// In App\Models\UserSalesVolume
protected static function booted()
{
static::created(function ($userSalesVolume) {
self::updateUserBusinessIfExists($userSalesVolume);
});
static::updated(function ($userSalesVolume) {
self::updateUserBusinessIfExists($userSalesVolume);
});
}
private static function updateUserBusinessIfExists($userSalesVolume)
{
// Prüfe ob UserBusiness für diesen Monat bereits existiert
$userBusiness = \App\Models\UserBusiness::where('user_id', $userSalesVolume->user_id)
->where('month', $userSalesVolume->month)
->where('year', $userSalesVolume->year)
->first();
if ($userBusiness) {
// Hole aktualisierte Werte aus UserSalesVolume
$user = \App\User::find($userSalesVolume->user_id);
$userBusiness->sales_volume_KP_points = $user->getUserSalesVolumeBy(
$userSalesVolume->month,
$userSalesVolume->year,
'sales_volume_KP_points'
);
$userBusiness->sales_volume_TP_points = $user->getUserSalesVolumeBy(
$userSalesVolume->month,
$userSalesVolume->year,
'sales_volume_TP_points'
);
$userBusiness->sales_volume_points_shop = $user->getUserSalesVolumeBy(
$userSalesVolume->month,
$userSalesVolume->year,
'sales_volume_points_shop'
);
$userBusiness->sales_volume_points_KP_sum = $user->getUserSalesVolumeBy(
$userSalesVolume->month,
$userSalesVolume->year,
'sales_volume_points_KP_sum'
);
$userBusiness->sales_volume_points_TP_sum = $user->getUserSalesVolumeBy(
$userSalesVolume->month,
$userSalesVolume->year,
'sales_volume_points_TP_sum'
);
$userBusiness->save();
\Log::info("UserBusiness auto-updated after UserSalesVolume change", [
'user_id' => $userSalesVolume->user_id,
'month' => $userSalesVolume->month,
'year' => $userSalesVolume->year
]);
}
}
Option B: Admin-Command für Nachberechnung
// php artisan business:update-month 12 2025
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\UserBusiness;
use App\User;
class BusinessUpdateMonth extends Command
{
protected $signature = 'business:update-month {month} {year}';
protected $description = 'Update UserBusiness for a specific month with current UserSalesVolume data';
public function handle()
{
$month = (int) $this->argument('month');
$year = (int) $this->argument('year');
$userBusinesses = UserBusiness::where('month', $month)
->where('year', $year)
->get();
$this->info("Updating {$userBusinesses->count()} UserBusiness entries...");
foreach ($userBusinesses as $userBusiness) {
$user = User::find($userBusiness->user_id);
if (!$user) continue;
$userBusiness->sales_volume_KP_points = $user->getUserSalesVolumeBy($month, $year, 'sales_volume_KP_points');
$userBusiness->sales_volume_TP_points = $user->getUserSalesVolumeBy($month, $year, 'sales_volume_TP_points');
$userBusiness->sales_volume_points_shop = $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_shop');
$userBusiness->sales_volume_points_KP_sum = $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_KP_sum');
$userBusiness->sales_volume_points_TP_sum = $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_TP_sum');
$userBusiness->save();
$this->info("Updated user {$userBusiness->user_id}");
}
$this->info("Done!");
}
}
🟢 Langfristige Lösung: Validierung & Monitoring
Dashboard-Widget für Inkonsistenzen:
// Finde alle User mit Differenzen zwischen UserSalesVolume und UserBusiness
$inconsistencies = DB::select("
SELECT
ub.id as user_business_id,
ub.user_id,
ub.month,
ub.year,
ub.sales_volume_points_KP_sum as stored_kp_sum,
(SELECT month_KP_points + month_shop_points
FROM user_sales_volumes
WHERE user_id = ub.user_id
AND month = ub.month
AND year = ub.year
ORDER BY id DESC
LIMIT 1) as actual_kp_sum,
ub.updated_at
FROM user_businesses ub
WHERE ub.month = ? AND ub.year = ?
HAVING stored_kp_sum != actual_kp_sum
", [12, 2025]);
📋 Aktualisierte Maßnahmen
| Priorität | Maßnahme | Aufwand | Status |
|---|---|---|---|
| 🔴 JETZT | UserBusiness für 12/2025 neu berechnen | 5 min | ⏭️ TODO |
| 🔴 Hoch | Inkonsistenzen für alle Monate finden | 30 min | ⏭️ TODO |
| 🟡 Mittel | Event Hook implementieren (Option A) | 2h | ⏭️ TODO |
| 🟡 Mittel | Update-Command erstellen (Option B) | 1h | ⏭️ TODO |
| 🟢 Niedrig | Dashboard-Validierung | 3h | ⏭️ TODO |
🚨 UMFANG DES PROBLEMS
Es sind mindestens 20 User im Dezember 2025 betroffen!
Top 10 betroffene User:
| User ID | Gespeichert | Aktuell | Differenz | UserBusiness ID |
|---|---|---|---|---|
| 1218 | 625 | 1115 | +490 | 21264 |
| 1001 | 172 | 365 | +193 | 21239 |
| 1156 | 646 | 837 | +191 | 21064 |
| 909 | 1353 | 1527 | +174 | 21215 |
| 675 | 0 | 151 | +151 | 21115 |
| 1313 | 283 | 431 | +148 | 21171 |
| 1364 | 398 | 518 | +120 | 21063 |
| 1225 | 649 | 755 | +106 | 21080 |
| 586 | 494 | 593 | +99 | 21071 |
| 3 | 11039 | 11112 | +73 | 20979 |
Muster: Alle wurden am 31.12.2025 ca. 03:00 Uhr berechnet (Cron-Job), danach wurden Gutschriften/Einträge hinzugefügt.
Gesamt-Differenz (nur Top 20): Ca. 2.000+ Punkte fehlen in gespeicherten Daten!
Empfehlung:
🔴 SOFORT (KRITISCH):
# Gesamten Monat neu berechnen (alle betroffenen User)
php artisan business:store-optimized 12 2025 --clear
🟡 MITTELFRISTIG (diese Woche):
- Event Hook implementieren (verhindert zukünftige Probleme)
- Prüfen ob auch andere Monate betroffen sind:
php artisan business:check-inconsistencies 11 2025 php artisan business:check-inconsistencies 10 2025
🟢 LANGFRISTIG:
- Dashboard-Monitoring für frühzeitige Erkennung
- Automatische E-Mail-Benachrichtigung bei Inkonsistenzen
[!] 10. Nicht zugeordnete Zahlungen/Punkte
Priorität: Hoch Bereich: Payment / Admin
Problem: Zahlungen ohne Zuordnung → Punkte verschwinden, keine Provision.
Anforderung:
- Admin-Hinweis bei nicht zugeordneten Zahlungen
- Manuelle Zuordnungsmöglichkeit
Technische Details:
| Parameter | Wert |
|---|---|
| Tabelle | user_sales_volumes |
| Status-Feld | status = 0 (not_assigned) |
| Admin-View | Dashboard oder separate Sektion |
Query für nicht zugeordnete Einträge:
$unassigned = UserSalesVolume::where('status', 0)
->with('user', 'order')
->orderBy('created_at', 'desc')
->get();
Umsetzung:
- Dashboard-Alert: Anzahl nicht zugeordneter Einträge anzeigen
- Admin-Seite: Liste aller nicht zugeordneten Einträge
- Zuordnungs-Modal:
- User auswählen (Dropdown/Suche)
- Status aktualisieren (1 = advisor_order, 2 = shoporder)
- Punkte werden bei nächster Berechnung berücksichtigt
[ ] 11. Monatsstatistik Erweiterungen
Priorität: Mittel Bereich: Dashboard / Team
Probleme:
- Teamumsatz wird seit Januar nicht angezeigt
- Neupartner/Abos nicht klickbar (keine Detailansicht)
Anforderungen:
| Feature | Beschreibung |
|---|---|
| Teamumsatz | Bug fixen - wird nicht angezeigt |
| Neupartner Details | Klick → Liste mit Name, E-Mail, Telefon, Generation, Mentor |
| Team-Abos Details | Klick → Liste mit Abo-Details |
| 1000-Punkte-Shops | Neue Kennzahl: Teampartner mit ≥1000 Punkte persönlichem Volumen |
| Aktuelle Provision | In Monatsstatistik anzeigen |
| Downline-Kontakte | Telefon, E-Mail, Adresse der eigenen Downline abrufbar (nicht nur VIPs) |
Technische Details:
| Parameter | Wert |
|---|---|
| Service | App\Services\LevelReportService |
| Controller | App\Http\Controllers\User\TeamController |
| View | resources/views/user/team/marketingplan.blade.php |
| Daten | user_business Tabelle |
1000-Punkte Query:
$count1000 = UserBusiness::where('month', $month)
->where('year', $year)
->where('sales_volume_points_sum', '>=', 1000)
->whereIn('user_id', $teamUserIds)
->count();
Klickbare Details (AJAX Modal):
// Route
Route::get('/team/new-partners/{month}/{year}', [TeamController::class, 'newPartnersDetail']);
// Response
return response()->json([
'partners' => $partners->map(fn($p) => [
'name' => $p->full_name,
'email' => $p->email,
'phone' => $p->phone,
'generation' => $p->generation,
'mentor' => $p->mentor->full_name ?? '-'
])
]);
[x] 12. Bezahllink Status-Unterscheidung
Priorität: Mittel Bereich: Payment / Admin
Problem: Unklar ob Payment-Link nur geklickt oder Zahlung wirklich durchgeführt.
Anforderung:
| Status | Bedeutung | Farbe |
|---|---|---|
link_sent |
Link wurde versendet | grau |
link_clicked |
Link wurde geklickt, keine Zahlung | orange |
payment_pending |
Zahlung in Bearbeitung | gelb |
paid |
Zahlung erfolgreich | grün |
failed |
Zahlung fehlgeschlagen | rot |
Technische Details:
| Parameter | Wert |
|---|---|
| Tabelle | shopping_payments |
| Feld | txaction (VARCHAR 20) |
| Service | App\Services\Payment |
Aktuelle Status (ungenau):
'paid' => 'paid'
'appointed' => 'open' // ← Zu ungenau
'failed' => 'failed'
'extern' => 'open' // ← Zu ungenau
Umsetzung:
- Neues Feld oder erweiterte
txaction-Werte - Bei Payment-Link-Aufruf: Status auf
link_clickedsetzen - Bei Payone-Callback: Status entsprechend aktualisieren
- Admin-View: Farbkodierung nach neuem Schema
[?] 13. Steuerberater-Modul
Priorität: Niedrig Status: Noch zu definieren
Notiz: Weitere Infos liegen vor - müssen noch spezifiziert werden.
TODO: Anforderungen dokumentieren
[X] 14. DHL Modul Erweiterungen
Status: ✅ ERLEDIGT Priorität: Hoch Bereich: Versand / packages/acme-laravel-dhl
Implementierte Funktionen:
| Feature | Status | Beschreibung |
|---|---|---|
| Storno-Etiketten UI | ✅ | Admin-Button für Label-Stornierung |
| Tracking-Abfrage | ✅ | Status automatisch abrufen |
| Tracking-Mail | ✅ | Kunde über Versand informieren |
Technische Details:
| Parameter | Wert |
|---|---|
| Package | packages/acme-laravel-dhl |
| Model | DhlShipment |
| Tabelle | dhl_package_shipments |
| Status-Feld | status (created/in_transit/delivered/canceled) |
| Tracking-Tabelle | dhl_tracking_events |
Vorhandene Jobs (bereits implementiert):
CreateShipmentJob✓CancelShipmentJob✓ (existiert, nutztcanCancel())CreateReturnLabelJob✓SyncTrackingJob✓ (Webhook-basiert)
Durchgeführte Implementierung:
-
✅ Admin-UI für Storno:
- Button "Label stornieren" in Bestellansicht (
_detail_dhl_shipments.blade.php) - Button im DHL Cockpit DataTable aktiviert (
DhlShipmentController.php) - JavaScript Handler für Storno-Button in beiden Views
- Dispatcht
CancelShipmentJob - Nur wenn
$shipment->canCancel()= true - Bestätigungsdialog mit Warnung vor ungültigem Label
- Button "Label stornieren" in Bestellansicht (
-
✅ Tracking-Mail an Kunde:
- Mail-Klasse:
App\Mail\MailDhlTracking(bereits vorhanden) - E-Mail Template:
resources/views/emails/dhl_tracking.blade.php - Trigger: Nach Status-Update auf
in_transit(automatisch via Cron) - Manueller Versand: Button in Admin-UI
- Inhalt: Sendungsnummer + Tracking-Link + Bestellnummer
- Übersetzungen: DE, EN, ES bereits vorhanden
- Tracking Status wird in Datenbank gespeichert (tracking_email_sent_at, tracking_email_type)
- Mail-Klasse:
-
✅ Cron für Tracking (Alternative zu Webhook):
- Command:
app/Console/Commands/DhlUpdateTracking.php - Signature:
php artisan dhl:update-tracking - Optionen:
--days=14: Sendungen der letzten X Tage aktualisieren--send-emails: Automatisch E-Mails bei Transit-Status senden--dry-run: Nur simulieren, keine Änderungen
- Cron-Job eingetragen in
app/Console/Kernel.php:- Täglich um 06:00 Uhr
- Mit automatischem E-Mail-Versand
withoutOverlapping()undrunInBackground()
- Statistik-Ausgabe: updated, failed, emails_sent, skipped
- Command:
-
✅ Retourenlabel-Button:
- Button "Retourenlabel erstellen" im DHL Cockpit aktiviert
- JavaScript Handler hinzugefügt
- Dispatcht
CreateReturnLabelJob - Nur für ausgehende Sendungen ohne vorhandene Retoure
Routen (bereits vorhanden):
Route::delete('/admin/dhl/shipment/{shipment}/cancel', ...) # Storno
Route::post('/admin/dhl/shipment/{shipment}/return-label', ...) # Retourenlabel
Route::post('/admin/dhl/shipment/{shipment}/update-tracking', ...) # Tracking Update
Route::post('/admin/dhl/shipment/{shipment}/send-tracking-email', ...) # E-Mail senden
DHL API Endpunkte:
DELETE /parcel/de/shipping/v2/orders/{shipmentNumber} # Storno
GET /parcel/de/tracking/v1/shipments/{shipmentNumber} # Tracking
Betroffene Dateien:
resources/views/admin/sales/_detail_dhl_shipments.blade.php- Storno-Button hinzugefügtresources/views/admin/dhl/cockpit.blade.php- JavaScript Handler erweitertapp/Http/Controllers/DhlShipmentController.php- Nutzt jetztDhlShipmentServiceapp/Services/DhlShipmentService.php- Erweitert umcancelShipment()Methodeapp/Jobs/CancelShipmentJob.php- Aktualisiert für neues Package-Modelapp/Console/Commands/DhlUpdateTracking.php- Tracking Command (bereits vorhanden)app/Console/Kernel.php- Cron-Job (bereits eingetragen)app/Mail/MailDhlTracking.php- E-Mail Klasse (bereits vorhanden)resources/views/emails/dhl_tracking.blade.php- E-Mail Template (bereits vorhanden)resources/lang/{de,en,es}/email.php- Übersetzungen (bereits vorhanden)
Fix für Model-Typ-Konflikt & Queue-Config:
CancelShipmentJobwurde vonApp\Models\DhlShipmentaufAcme\Dhl\Models\DhlShipmentmigriert- Nutzt jetzt
Acme\Dhl\Services\ShippingService::cancelLabel()aus dem neuen Package - Verwendet
dhl_shipment_nostattshipment_number(korrektes Feld-Mapping) DhlShipmentService::cancelShipment()hinzugefügt:- Prüft
DHL_USE_QUEUEConfig-Einstellung - Verwendet Queue (
CancelShipmentJob) wenn aktiviert - Führt synchron aus (
ShippingService::cancelLabel()) wenn deaktiviert - Konsistentes Verhalten wie bei
createShipment()
- Prüft
- Controller nutzt jetzt Service statt direkt Job zu dispatchen:
DhlShipmentController::cancel()ruftDhlShipmentService::cancelShipment()auf- Automatische Entscheidung zwischen Queue/Sync basierend auf Config
Verwendung:
Manuell:
# Tracking aktualisieren (Simulation)
php artisan dhl:update-tracking --dry-run
# Tracking aktualisieren mit E-Mail-Versand
php artisan dhl:update-tracking --days=7 --send-emails
# Nur letzte 3 Tage aktualisieren
php artisan dhl:update-tracking --days=3
# NEU: Test-E-Mail an eigene Adresse
php artisan dhl:update-tracking --send-emails --test-email=admin@firma.de
# NEU: Nur für bestimmte Bestellung
php artisan dhl:update-tracking --send-emails --order=45078
Automatisch via Cron:
- Läuft täglich um 06:00 Uhr
- Aktualisiert Sendungen der letzten 14 Tage
- Sendet automatisch E-Mails bei Status-Änderung zu "in_transit"
- Verhindert Überlappungen mit
withoutOverlapping()
NEU: Mehrere Sendungen in einer E-Mail:
- Wenn eine Bestellung mehrere Labels hat, werden alle in einer E-Mail zusammengefasst
- Automatisch beim manuellen Versand über Admin-Button
- Automatisch beim Cronjob-Versand
- Zeigt "Paket 1, Paket 2, Paket 3" mit jeweiliger Tracking-Nummer
- Markiert alle Sendungen als versendet
NEU: Versand-Status in Bestelldetails:
- Zeigt wann E-Mail versendet wurde
- Zeigt ob automatisch (Cronjob) oder manuell (Admin)
- Icons: 🤖 Automatisch / 👤 Manuell
NEU: E-Mail-Feld für bestehende Sendungen nachfüllen:
# Dry-Run (nur simulieren)
php artisan dhl:backfill-emails --dry-run
# Tatsächlich ausführen
php artisan dhl:backfill-emails
NEU: E-Mail + Postnummer bei Label-Erstellung:
- ✅ Migration hinzugefügt:
2026_01_23_140000_add_email_and_postnumber_to_dhl_shipments.php - ✅ Neue Felder in
dhl_package_shipments:email,postnumber - ✅ Model
DhlShipmenterweitert um beide Felder - ✅ Formular-Feld für E-Mail hinzugefügt in
modal_in_order_shipment.blade.php - ✅ E-Mail-Feld ist Pflichtfeld mit Validierung (type="email", required)
- ✅ Vorbefüllung mit Billing-E-Mail aus
order->shopping_user->email - ✅ Postnummer-Feld bereits vorhanden (optional für Packstation)
- ✅ Controller-Validierung erweitert:
shipping_email(required),shipping_postnumber(nullable) - ✅
DhlDataHelperübergibt E-Mail + Postnummer an ShippingService - ✅
ShippingService::createShipmentRecord()speichert beide Felder in DB - ✅ Daten werden sowohl direkt als auch im JSON
recipientgespeichert
Zweck der Felder:
email: Wird für DHL Benachrichtigungen und Tracking-E-Mails verwendetpostnumber: DHL Postnummer (6-10 Stellen) für Packstation/Paketbox-Lieferungen
E-Mail-Button-Logik:
- ✅ Button wird angezeigt, wenn Sendung eine
dhl_shipment_nohat UND eine E-Mail verfügbar ist - ✅ Priorisierung: Shipment-Email > Shopping-User-Email
- ✅
canSendTrackingEmail()prüft zuerst das neueemailFeld - ✅ Fallback auf
shopping_user->emailwenn Shipment-Email leer - ✅ Button funktioniert in beiden Views: Bestelldetails + DHL Cockpit
E-Mail-Versand-Priorisierung:
- Test-E-Mail (falls angegeben im Cronjob mit
--test-email) - Shipment-Email (aus
dhl_package_shipments.email) - Shopping-User-Email (Fallback aus
shopping_users.email)
Betroffene Dateien (E-Mail + Postnummer):
database/migrations/2026_01_23_140000_add_email_and_postnumber_to_dhl_shipments.php- NEUpackages/acme-laravel-dhl/src/Models/DhlShipment.php- fillable + canSendTrackingEmail() erweitertresources/views/admin/dhl/modal_in_order_shipment.blade.php- E-Mail-Feld hinzugefügtapp/Http/Controllers/DhlShipmentController.php- Validierung + E-Mail-Priorisierungpackages/acme-laravel-dhl/src/Services/ShippingService.php- Speicherung erweitertapp/Console/Commands/DhlUpdateTracking.php- E-Mail-Priorisierung im Cronjobapp/Services/DhlDataHelper.php- Übergibt E-Mail + Postnummer (bereits vorhanden)app/Services/DhlModalService.php- Liest Formularfelder (bereits vorhanden)resources/views/admin/dhl/show.blade.php- E-Mail-Button + JavaScript Handlerresources/views/admin/sales/_detail_dhl_shipments.blade.php- E-Mail-Button + Handler (bereits vorhanden)resources/views/admin/dhl/modal_in_shipment_info.blade.php- E-Mail-Button im Modalapp/Console/Commands/DhlBackfillEmails.php- Command zum Nachfüllen (NEU)
E-Mail-Button Standorte: ✅ Bestelldetails (_detail_dhl_shipments.blade.php) ✅ DHL Cockpit (cockpit.blade.php) ✅ DHL Detail-Seite (show.blade.php) ✅ Modal nach Label-Erstellung (modal_in_shipment_info.blade.php)
NEU: Return Label (Retourenlabel) Funktionalität:
✅ Button in allen DHL Views hinzugefügt
✅ Controller mit Sync/Async Unterstützung (DHL_USE_QUEUE)
✅ Job aktualisiert für neues DHL Package
✅ Automatisches Adress-Tausch (Kunde → Absender, Lager → Empfänger)
✅ Prüfung ob bereits Retoure existiert
✅ Nur für ausgehende Sendungen verfügbar
✅ API-Fix (23.01.2026): ReturnsService statt ShippingService verwenden
✅ Korrekter DHL Returns API Endpunkt: /parcel/de/returns/v1/labels
✅ Korrekte Payload-Struktur für Returns API
✅ Verbesserte Validierung und Fehlerbehandlung
✅ Erweitertes Logging für Debugging
✅ Country-Code Fix: Automatische Konvertierung 2-stellig → 3-stellig (DE → DEU)
✅ Fallback-Implementierung: Automatischer Fallback zu regulärer Shipping-API (V07PAK) bei fehlenden Returns-API Berechtigungen
✅ Intelligente Fehlerbehandlung für Auth-Fehler (401/403)
✅ Transparentes Logging welche Methode verwendet wird
✅ Fallback-Fixes (23.01.2026): Country-Code Konvertierung (3→2 Buchstaben), Dimensions hinzugefügt, print_format gesetzt
✅ Automatische Adress-Konvertierung für ShippingService-Kompatibilität
✅ V01PAK statt V07PAK: V07PAK nicht verfügbar, verwende V01PAK (Standard DHL Paket) mit vertauschten Adressen
✅ Return-Label Fixes (23.01.2026 - 17:30): Type-Update korrigiert ($result['shipment'] statt $result['shipmentId'])
✅ Doppelklick-Schutz für Return-Button implementiert
✅ Existierende Return-Labels (ID 18, 19) manuell korrigiert zu type='return'
✅ Packstation Return-Label mit Billing-Adresse (23.01.2026 - 19:00): Return-Labels für Packstation möglich!
✅ Bei Packstation-Sendungen wird Rechnungsadresse als Return-Absender verwendet
✅ Automatische Packstation-Erkennung (postNumber-Check)
✅ getBillingAddressForReturn() extrahiert Straße + Hausnummer aus billing_address
✅ Gleiche Logik in DhlShipmentController + CreateReturnLabelJob
✅ UI: Return-Button aktiv für ALLE Outbound-Sendungen (Blockierung entfernt)
Return Label Button Standorte: ✅ Bestelldetails (_detail_dhl_shipments.blade.php) - NEU ✅ DHL Cockpit (cockpit.blade.php) - funktioniert ✅ DHL Detail-Seite (show.blade.php) - aktiviert ✅ Nur sichtbar wenn: type='outbound' UND keine Retoure existiert
NEU: Return Label Visuelle Hervorhebung (23.01.2026):
✅ Return-Etiketten deutlich erkennbar mit oranger Farbgebung
✅ Orange "RETOURE" Badge (statt blau) in allen Listen
✅ Orange ID-Links mit Undo-Icon in allen Tabellen
✅ Zeilen-Highlighting in DataTable (orangener Hintergrund + linker Border)
✅ Zeilen-Highlighting in Order-Details (orangener Hintergrund)
✅ Größeres, fetteres Badge in Detail-Ansicht (show.blade.php)
✅ CSS-Klasse return-shipment für DataTable-Zeilen
✅ Konsistente orange Farbgebung (badge-warning, text-warning, #ffc107)
✅ Return-Etiketten bekommen KEINEN "Retourenlabel erstellen" Button
Betroffene Dateien (Styling):
app/Http/Controllers/DhlShipmentController.php- DataTable Spalten (ID, Typ)resources/views/admin/dhl/cockpit.blade.php- CSS + JS für Zeilen-Highlightingresources/views/admin/dhl/show.blade.php- Header Badge + Iconresources/views/admin/sales/_detail_dhl_shipments.blade.php- Zeilen-Style + Badge
Dokumentation: dev/23-01-2026/dhl-return-label-styling.md
[X] 12. Payment Link Status - Legende & Race Condition Fix
Priorität: Hoch ✅ ERLEDIGT (28.01.2026) Bereich: Backend / Payment / Payone Integration
Problem 1: Keine Status-Übersicht Benutzer sahen Payment Status-Badges ohne Erklärung in der Übersicht.
Problem 2: Race Condition Bug
Wenn Payone Requests in falscher Reihenfolge ankamen (paid vor appointed), wurde der Status falsch gesetzt:
paidkam zuerst → Status = 10 (korrekt)appointedkam danach → Status = 4 (FALSCH - überschrieb paid!)
Problem 3: Status nur für Abos
Payment Link Status 10 (link_paid) wurde nur für Abo-Bestellungen gesetzt, normale Bestellungen blieben auf Status 4.
Lösung implementiert:
1. Dynamische Status-Legende
✅ Status-Legende oberhalb der Payment Links Tabelle
✅ Vollständig dynamisch aus OrderPaymentService::getStatusBadgeClasses()
✅ Mehrsprachig über Laravel Translation-System (DE, EN, ES)
✅ Automatische Synchronisation mit tatsächlichen Status-Definitionen
Status-Hierarchie:
$statuses = [
0 => 'link_sent', // default (grau)
1 => 'link_openly', // info (blau)
2 => 'link_check', // warning (gelb)
3 => 'link_pending', // warning (gelb)
4 => 'link_appointed', // warning (gelb)
5 => 'link_failed', // danger (rot)
6 => 'link_canceled', // danger (rot)
10 => 'link_paid', // secondary (grau) - FINAL STATE
];
2. Payone Race Condition Fix
✅ Prioritätsprüfung für txaction Updates in PayoneController
✅ paid (Priorität 10) kann nicht durch appointed (Priorität 1) überschrieben werden
✅ Logging bei übersprungenen Updates für Nachvollziehbarkeit
Prioritäten:
$txaction_priority = [
'appointed' => 1,
'pending' => 2,
'failed' => 3,
'paid' => 10, // höchste Priorität - finaler Status
];
3. Status Update für ALLE Bestellungen
✅ Payment::paymentStatusPaidAction setzt Status 10 für ALLE Zahlungen
✅ Nicht mehr nur für Abo-Bestellungen beschränkt
✅ Konsistente Status-Vergabe über alle Bestellungstypen
4. ShoppingInstance Model Fix
✅ Primary Key korrekt definiert: identifier (string, non-incrementing)
✅ Laravel Eloquent funktioniert jetzt korrekt mit der Tabelle
Model-Konfiguration:
protected $primaryKey = 'identifier';
public $incrementing = false;
protected $keyType = 'string';
5. Artisan Command für Datenbank-Cleanup
✅ Neuer Command: php artisan payment:fix-link-status
✅ Dry-Run Modus (--dry-run) zur sicheren Prüfung
✅ Korrigiert historische Payment Links mit falschem Status
✅ Detaillierte Zusammenfassung und Logging
Command Statistik (28.01.2026):
- ✅ 2.117 Payment Links korrigiert (Status 4 → 10)
- ✅ 490 bereits korrekt (durch neue Logik)
- ⚠️ 12.754 ShoppingInstances nicht gefunden (bereits gelöscht nach Abschluss)
Implementierte Dateien:
-
OrderPaymentService (
app/Services/OrderPaymentService.php)- Neue Methode:
getStatusBadgeClasses()für zentrale Status-Definition getStatusBadge()nutzt jetzt zentrale Definition
- Neue Methode:
-
Payment Links View (
resources/views/user/order/payment/index.blade.php)- Dynamische Status-Legende über
@foreachgeneriert - Mehrsprachige Labels via
__('payment.' . $statusKey)
- Dynamische Status-Legende über
-
PayoneController (
app/Http/Controllers/Api/PayoneController.php)- Prioritätsprüfung vor
txactionUpdate - Logging bei übersprungenen Updates
- Prioritätsprüfung vor
-
Payment Service (
app/Services/Payment.php)- Status 10 wird jetzt für ALLE Bestellungen gesetzt (Zeile 262-267)
- Nicht mehr nur innerhalb der
is_aboBedingung
-
ShoppingInstance Model (
app/Models/ShoppingInstance.php)- Primary Key korrekt konfiguriert
- Laravel Eloquent funktioniert mit identifier-basierter Tabelle
-
Artisan Command (
app/Console/Commands/FixPaymentLinkStatus.php)- Findet alle bezahlten Orders mit falschem ShoppingInstance Status
- Dry-Run Modus für sichere Prüfung
- Detaillierte Ausgabe und Statistiken
-
Übersetzungen (korrigiert)
- DE:
resources/lang/de/payment.php- vollständig - EN:
resources/lang/en/payment.php- vollständig - ES:
resources/lang/es/payment.php- 2 Übersetzungen korrigiert:link_paid: "pagado" → "Pago exitoso"link_check: "Pago en curso" → "Pago en revisión"
- DE:
Verwendung:
# Prüfen, welche Payments korrigiert würden
php artisan payment:fix-link-status --dry-run
# Tatsächliche Korrektur durchführen
php artisan payment:fix-link-status
Vorteile:
- ✅ Benutzer verstehen Payment Status auf einen Blick
- ✅ Keine Race Condition mehr bei Payone Requests
- ✅ Konsistente Status-Vergabe für alle Bestellungstypen
- ✅ Mehrsprachig und automatisch synchron mit Backend-Logik
- ✅ Historische Daten wurden bereinigt
- ✅ Wartbar durch zentrale Status-Definition
Dokumentation: dev/28-01-2026/payment-status-legend-and-race-condition-fix.md
ZUSAMMENFASSUNG
| # | Aufgabe | Priorität | Komplexität | Bereich |
|---|---|---|---|---|
| 1 | News Links + Datei-Auswahl | Hoch | Niedrig | Frontend |
| 2 | Points DECIMAL | Hoch | Hoch | DB/Backend |
| 3 | Vorkasse TXID Hinweis | Hoch | Niedrig | Frontend |
| 4 | Packstation/Postnummer | Mittel | Mittel | DB/Frontend |
| 5 | Set-Produkte (wie Inhaltsstoffe) | Mittel | Hoch | DB/Backend |
| 6 | Mehrsprachigkeit PDFs | Hoch | Mittel | Backend |
| ✅ ERLEDIGT | ||||
| 8 | Französisch | Mittel | Niedrig | i18n |
| 9 | Gutschriften Punkte Bug | Hoch | Mittel | Backend |
| 10 | Nicht zugeordnete Zahlungen | Hoch | Mittel | Backend |
| 11 | Monatsstatistik Erweiterungen | Mittel | Mittel | Backend |
| ✅ ERLEDIGT | ||||
| 13 | Steuerberater | Niedrig | ? | TBD |
| 14 | DHL UI + Tracking-Mail | Hoch | Mittel | Package |
EMPFOHLENE REIHENFOLGE
Phase 1: Quick Wins (Frontend, niedrige Komplexität)
- #1 News Links ERLEDIGT 22.01.2026
- #3 Vorkasse TXID Hinweis ERLEDIGT 22.01.2026
- #12 Bezahllink Status ✅ ERLEDIGT 28.01.2026
Phase 2: Kritische Bugs (Provisionen betroffen)
- #9 Gutschriften Punkte Bug
- #10 Nicht zugeordnete Zahlungen
Phase 3: Infrastruktur (DB-Änderungen)
- #2 Points DECIMAL (benötigt Migration + Testing) ERLEDIGT 22.01.2026
- #7 Stornorechnungen mit Punktekorrektur ✅ ERLEDIGT 06.02.2026
Phase 4: Features
- #6 Mehrsprachigkeit PDFs
- #14 DHL UI + Tracking-Mail ERLEDIGT 23.01.2026
- #11 Monatsstatistik Erweiterungen
Phase 5: Langfristig
- #4 Packstation/Postnummer ERLEDIGT 22.01.2026
- #5 Set-Produkte Bundles ERLEDIGT 22.01.2026
- #8 Französisch
- #13 Steuerberater-Modul