2302 lines
77 KiB
Markdown
2302 lines
77 KiB
Markdown
# 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:**
|
|
|
|
1. **Migration:** `2026_01_23_120458_add_file_links_to_dashboard_news_table.php`
|
|
|
|
- Neues JSON-Feld `file_links` in `dashboard_news` Tabelle
|
|
|
|
2. **Model:** `app/Models/DashboardNews.php`
|
|
|
|
- `file_links` zu `$fillable` und `$casts` hinzugefügt
|
|
- Neue Methoden: `getFileLinks($lang)`, `hasFileLinks($lang)`
|
|
|
|
3. **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
|
|
|
|
4. **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
|
|
|
|
5. **Ü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`
|
|
|
|
**JSON-Struktur:**
|
|
|
|
```json
|
|
{
|
|
"de": [
|
|
{ "file_id": 123, "label": "Preisliste herunterladen" },
|
|
{ "file_id": 456, "label": "Produktkatalog öffnen" }
|
|
],
|
|
"en": [{ "file_id": 789, "label": "Download Price List" }]
|
|
}
|
|
```
|
|
|
|
**Verwendung im Admin:**
|
|
|
|
1. News bearbeiten → Zum jeweiligen Sprach-Tab scrollen
|
|
2. "Datei-Link hinzufügen" klicken
|
|
3. Label eingeben (z.B. "Preisliste herunterladen")
|
|
4. Datei aus Dropdown auswählen
|
|
5. 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`:**
|
|
|
|
```sql
|
|
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`:**
|
|
|
|
```sql
|
|
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`:**
|
|
|
|
```sql
|
|
ALTER TABLE products
|
|
MODIFY points DECIMAL(10,2);
|
|
```
|
|
|
|
**Weitere Models mit `points`:**
|
|
|
|
- `App\Models\ShoppingOrder` → `points`
|
|
- `App\Models\ShoppingOrderItem` → `points`
|
|
- `App\Models\ShoppingCollectOrder` → `points`
|
|
- `App\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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
1. **Checkout:** Alert-Box mit Verwendungszweck-Hinweis bei Vorkasse-Auswahl
|
|
2. **E-Mail:** Hervorgehobener Block mit Bankdaten + TXID als Verwendungszweck
|
|
3. **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:**
|
|
|
|
1. ✅ **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`
|
|
|
|
2. ✅ **Model angepasst** (`app/Models/ShoppingUser.php`)
|
|
|
|
- Feld im `$fillable` Array
|
|
- Methode `hasPostnumber()` hinzugefügt
|
|
|
|
3. ✅ **Checkout-Formular** (`resources/views/web/templates/checkout.blade.php`)
|
|
|
|
- Eingabefeld für Postnummer nach Telefon-Feld
|
|
- Placeholder: "12345678"
|
|
- JavaScript-Validierung für Packstation-Format
|
|
|
|
4. ✅ **DHL Modal** (`resources/views/admin/dhl/modal_in_order_shipment.blade.php`)
|
|
|
|
- Postnummer-Feld hinzugefügt
|
|
|
|
5. ✅ **DHL Service** (`app/Services/DhlModalService.php`)
|
|
|
|
- Postnummer wird korrekt an DHL API übergeben
|
|
|
|
6. ✅ **Kundendetail-Ansicht** (`resources/views/admin/customer/_customer_detail.blade.php`)
|
|
|
|
- Postnummer wird mit Badge angezeigt
|
|
- Hinweistext für Packstation-Lieferung
|
|
|
|
7. ✅ **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
|
|
|
|
8. ✅ **Lieferschein-PDF** (`resources/views/pdf/delivery.blade.php`)
|
|
|
|
- Postnummer wird fett gedruckt in der Lieferadresse angezeigt
|
|
- Format: "DHL Postnummer: 12345678"
|
|
|
|
9. ✅ **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)
|
|
|
|
10. ✅ **User-Account Formular** (`resources/views/user/user_form.blade.php`)
|
|
|
|
- Migration für `user_accounts` Tabelle erstellt und ausgeführt
|
|
- Datei: `database/migrations/2026_01_23_102622_add_shipping_postnumber_to_user_accounts_table.php`
|
|
- Model `UserAccount` im `$fillable` Array erweitert
|
|
- Postnummer-Feld nach shipping_phone hinzugefügt
|
|
- Identische Alert-Box wie im Admin-Formular
|
|
- Identisches JavaScript für Ein-/Ausblendung
|
|
|
|
11. ✅ **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
|
|
|
|
12. ✅ **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 User
|
|
- `shoppingUserAuthData()` erweitert (Zeile 418 + 430): Übertragung für Salescenter-Bestellungen
|
|
- Postnummer wird jetzt korrekt im Checkout-Formular angezeigt
|
|
|
|
13. ✅ **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
|
|
|
|
14. ✅ **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_postnumber` hinzugefügt (2 Stellen)
|
|
- Für `same_as_billing` true/false Szenarien
|
|
|
|
## 🔍 **TIEFENPRÜFUNG DURCHGEFÜHRT - Weitere kritische Lücken gefunden und geschlossen!**
|
|
|
|
15. ✅ **KRITISCHE CONTROLLER/SERVICES KORRIGIERT** - Datenübertragung sichergestellt:
|
|
|
|
- ✅ `app/Services/UserUtil.php` (Zeile 101)
|
|
- ShoppingUser-Erstellung aus UserAccount
|
|
- `shipping_postnumber` fehlte komplett!
|
|
- ✅ `app/Services/AboOrderCart.php` (Zeilen 277 + 289)
|
|
- Abo-Bestellungen: ShoppingUser aus UserAccount
|
|
- `shipping_postnumber` fehlte an 2 Stellen (same_as_billing true/false)
|
|
- ✅ `app/Services/PaymentHelper.php` (Zeile 115)
|
|
- Payment ShoppingUser Update
|
|
- `shipping_postnumber` fehlte komplett!
|
|
|
|
16. ✅ **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_title`
|
|
- `packstation_alert_intro`
|
|
- `packstation_alert_street`
|
|
- `packstation_alert_street_example`
|
|
- `packstation_alert_location`
|
|
- `packstation_alert_not_home`
|
|
- `packstation_alert_footer`
|
|
|
|
**DHL API Integration:**
|
|
|
|
```php
|
|
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):**
|
|
|
|
```sql
|
|
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`:**
|
|
|
|
```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 auflisten
|
|
- `resources/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` |
|
|
| Mail | `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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
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=false` ODER `cancellation_id IS NOT NULL`
|
|
- `user_cancellation_invoice()`: Findet Stornorechnung
|
|
- Logik: `cancellation=true` UND `cancellation_id IS NULL`
|
|
- `user_invoices()`: Alle Rechnungen (Original + Storno)
|
|
|
|
Neue Hilfsmethoden:
|
|
|
|
- `isCancellationInvoice()`: Prüft, ob Stornorechnung existiert
|
|
- `getCancellationInvoice()`: 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):
|
|
|
|
```php
|
|
Route::post('/admin/sales/invoice/cancellation',
|
|
'SalesController@invoiceCancellation')
|
|
->name('admin_sales_invoice_cancellation');
|
|
```
|
|
|
|
#### Datenbank-Struktur
|
|
|
|
**Bestehende Felder genutzt:**
|
|
|
|
`user_invoices`:
|
|
|
|
- `cancellation` (boolean): `true` bei Original UND Stornorechnung
|
|
- `cancellation_id` (int): Nur bei Originalrechnung (zeigt auf Storno), `NULL` bei Stornorechnung
|
|
- `cancellation_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 = 6` für Storno-Einträge
|
|
- Negative `points` und `total_net`
|
|
|
|
`shopping_orders`:
|
|
|
|
- `txaction = 'cancelled'` (Zahlungsstatus)
|
|
- `shipped = 10` (Versandstatus "storniert")
|
|
|
|
#### Workflow
|
|
|
|
1. **Admin klickt "Stornorechnung erstellen"**
|
|
2. **Modal öffnet sich** → Datum auswählen, optional E-Mail aktivieren
|
|
3. **POST an `admin_sales_invoice_cancellation`**
|
|
4. **Controller validiert** → keine Doppel-Stornos
|
|
5. **Repository erstellt Storno:**
|
|
- Neue Rechnungsnummer
|
|
- PDF-Generierung (Rechnung + Lieferschein)
|
|
- DB-Eintrag für Stornorechnung
|
|
- Originalrechnung markieren
|
|
- Bestellstatus aktualisieren
|
|
6. **Service korrigiert Punkte:**
|
|
- Neuer `UserSalesVolume`-Eintrag (negativ)
|
|
- MLM-Struktur neu berechnen
|
|
- Monatssummen aktualisieren
|
|
7. **Redirect mit Success-Message**
|
|
8. **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 `syslog` und `\Log`
|
|
- ✅ **Performance**: Memory-optimiert, nutzt `TreeCalcBotOptimized`
|
|
- ✅ **UI-Konsistenz**: Roter Farbcode durchgängig (danger)
|
|
|
|
#### Testing-Checkliste
|
|
|
|
- [x] Stornorechnung erstellen für Berater-Bestellung
|
|
- [x] Stornorechnung erstellen für Shop-Bestellung
|
|
- [x] Stornorechnung erstellen für Homeparty
|
|
- [x] Stornorechnung erstellen für Sammelbestellung
|
|
- [x] PDF-Download DE, EN, ES
|
|
- [x] Punktekorrektur in `user_sales_volumes` prüfen
|
|
- [x] Monatssummen in Business Points prüfen
|
|
- [x] Upline-Punkte prüfen (MLM-Hierarchie)
|
|
- [x] Bestellstatus "Storniert" anzeigen
|
|
- [x] Versandstatus "Storniert" (nur bei offenen)
|
|
- [x] Doppel-Storno verhindert
|
|
- [x] 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` (Methode `invoiceCancellation()`)
|
|
- `app/Http/Controllers/FileController.php` (Stornorechnung laden)
|
|
- `app/Repositories/InvoiceRepository.php` (Methode `createCancellation()`)
|
|
- `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.php`
|
|
- `resources/views/pdf/cancellation-detail.blade.php`
|
|
- `resources/views/pdf/cancellation_delivery.blade.php`
|
|
- `resources/views/pdf/cancellation-detail-homeparty.blade.php`
|
|
- `resources/views/pdf/cancellation-detail-collection.blade.php`
|
|
|
|
**Übersetzungen:**
|
|
|
|
- `resources/lang/de/pdf.php`
|
|
- `resources/lang/en/pdf.php`
|
|
- `resources/lang/es/pdf.php`
|
|
- `resources/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
|
|
|
|
1. **Saubere Architektur**: Controller → Repository → Service → Model
|
|
2. **Eloquent Relations**: Komplexe WHERE-Queries in Relations gekapselt
|
|
3. **DB-Transaktionen**: Atomare Operationen mit Rollback
|
|
4. **Service-Layer**: Business-Logik getrennt von Data-Access
|
|
5. **Code-Qualität**: Pint-formatiert, PSR-12 konform
|
|
6. **Fehlerbehandlung**: Try-Catch mit aussagekräftigen Fehlermeldungen
|
|
7. **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:**
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```php
|
|
0 => 'not_assigned'
|
|
1 => 'advisor_order'
|
|
2 => 'shoporder'
|
|
3 => 'shoporder_pending'
|
|
4 => 'credit' // ← Gutschrift
|
|
5 => 'registration'
|
|
```
|
|
|
|
**Debugging-Schritte:**
|
|
|
|
1. Query für User mit `status = 4` im betroffenen Monat prüfen:
|
|
```php
|
|
UserSalesVolume::where('user_id', $userId)
|
|
->where('month', 12)->where('year', 2025)
|
|
->where('status', 4)->get();
|
|
```
|
|
2. Berechnung in `getPointsKPSum()` / `getPointsTPSum()` validieren
|
|
3. Team-View Query vs. Admin-View Query vergleichen
|
|
4. Prüfen ob `status_points` korrekt 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**
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
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:
|
|
|
|
1. Monat 12/2025 läuft
|
|
2. User 1218 hat mehrere UserSalesVolume-Einträge
|
|
3. `reCalculateSalesPointsVolume()` wird ausgeführt → letzter Eintrag hat z.B. 545 Punkte
|
|
4. **NACH** der letzten Berechnung wird eine Gutschrift (32.758 Punkte) hinzugefügt
|
|
5. **ABER**: `reCalculateSalesPointsVolume()` wird NICHT erneut aufgerufen
|
|
6. ❌ 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**
|
|
|
|
```php
|
|
// 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.
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```sql
|
|
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_points` gesetzt? (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:**
|
|
|
|
```sql
|
|
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_sum` zu den Punkten im letzten UserSalesVolume-Eintrag?
|
|
|
|
**3. Live-Berechnung testen:**
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// Ü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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
1. ✅ Technische Analyse abgeschlossen (28.01.2026)
|
|
2. ✅ Datenbank-Analyse durchgeführt (28.01.2026)
|
|
3. ✅ Ursache identifiziert (siehe unten)
|
|
4. ⏭️ Lösung implementieren
|
|
5. ⏭️ 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 0` lä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**
|
|
|
|
```bash
|
|
# UserBusiness für betroffenen Monat neu berechnen
|
|
php artisan business:store-optimized 12 2025 --clear
|
|
```
|
|
|
|
**Oder nur für User 1218:**
|
|
|
|
```php
|
|
// 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
|
|
|
|
```php
|
|
// 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
|
|
// 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:
|
|
|
|
```php
|
|
// 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):**
|
|
|
|
```bash
|
|
# Gesamten Monat neu berechnen (alle betroffenen User)
|
|
php artisan business:store-optimized 12 2025 --clear
|
|
```
|
|
|
|
🟡 **MITTELFRISTIG (diese Woche):**
|
|
|
|
1. Event Hook implementieren (verhindert zukünftige Probleme)
|
|
2. Prüfen ob auch andere Monate betroffen sind:
|
|
```bash
|
|
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:**
|
|
|
|
```php
|
|
$unassigned = UserSalesVolume::where('status', 0)
|
|
->with('user', 'order')
|
|
->orderBy('created_at', 'desc')
|
|
->get();
|
|
```
|
|
|
|
**Umsetzung:**
|
|
|
|
1. **Dashboard-Alert:** Anzahl nicht zugeordneter Einträge anzeigen
|
|
2. **Admin-Seite:** Liste aller nicht zugeordneten Einträge
|
|
3. **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:**
|
|
|
|
```php
|
|
$count1000 = UserBusiness::where('month', $month)
|
|
->where('year', $year)
|
|
->where('sales_volume_points_sum', '>=', 1000)
|
|
->whereIn('user_id', $teamUserIds)
|
|
->count();
|
|
```
|
|
|
|
**Klickbare Details (AJAX Modal):**
|
|
|
|
```php
|
|
// 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):**
|
|
|
|
```php
|
|
'paid' => 'paid'
|
|
'appointed' => 'open' // ← Zu ungenau
|
|
'failed' => 'failed'
|
|
'extern' => 'open' // ← Zu ungenau
|
|
```
|
|
|
|
**Umsetzung:**
|
|
|
|
1. Neues Feld oder erweiterte `txaction`-Werte
|
|
2. Bei Payment-Link-Aufruf: Status auf `link_clicked` setzen
|
|
3. Bei Payone-Callback: Status entsprechend aktualisieren
|
|
4. 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, nutzt `canCancel()`)
|
|
- `CreateReturnLabelJob` ✓
|
|
- `SyncTrackingJob` ✓ (Webhook-basiert)
|
|
|
|
**Durchgeführte Implementierung:**
|
|
|
|
1. ✅ **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
|
|
|
|
2. ✅ **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)
|
|
|
|
3. ✅ **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()` und `runInBackground()`
|
|
- Statistik-Ausgabe: updated, failed, emails_sent, skipped
|
|
|
|
4. ✅ **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):**
|
|
|
|
```php
|
|
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:**
|
|
|
|
1. `resources/views/admin/sales/_detail_dhl_shipments.blade.php` - Storno-Button hinzugefügt
|
|
2. `resources/views/admin/dhl/cockpit.blade.php` - JavaScript Handler erweitert
|
|
3. `app/Http/Controllers/DhlShipmentController.php` - Nutzt jetzt `DhlShipmentService`
|
|
4. `app/Services/DhlShipmentService.php` - **Erweitert um `cancelShipment()` Methode**
|
|
5. `app/Jobs/CancelShipmentJob.php` - **Aktualisiert für neues Package-Model**
|
|
6. `app/Console/Commands/DhlUpdateTracking.php` - Tracking Command (bereits vorhanden)
|
|
7. `app/Console/Kernel.php` - Cron-Job (bereits eingetragen)
|
|
8. `app/Mail/MailDhlTracking.php` - E-Mail Klasse (bereits vorhanden)
|
|
9. `resources/views/emails/dhl_tracking.blade.php` - E-Mail Template (bereits vorhanden)
|
|
10. `resources/lang/{de,en,es}/email.php` - Übersetzungen (bereits vorhanden)
|
|
|
|
**Fix für Model-Typ-Konflikt & Queue-Config:**
|
|
|
|
- `CancelShipmentJob` wurde von `App\Models\DhlShipment` auf `Acme\Dhl\Models\DhlShipment` migriert
|
|
- Nutzt jetzt `Acme\Dhl\Services\ShippingService::cancelLabel()` aus dem neuen Package
|
|
- Verwendet `dhl_shipment_no` statt `shipment_number` (korrektes Feld-Mapping)
|
|
- **`DhlShipmentService::cancelShipment()` hinzugefügt:**
|
|
- Prüft `DHL_USE_QUEUE` Config-Einstellung
|
|
- Verwendet Queue (`CancelShipmentJob`) wenn aktiviert
|
|
- Führt synchron aus (`ShippingService::cancelLabel()`) wenn deaktiviert
|
|
- Konsistentes Verhalten wie bei `createShipment()`
|
|
- **Controller nutzt jetzt Service statt direkt Job zu dispatchen:**
|
|
- `DhlShipmentController::cancel()` ruft `DhlShipmentService::cancelShipment()` auf
|
|
- Automatische Entscheidung zwischen Queue/Sync basierend auf Config
|
|
|
|
**Verwendung:**
|
|
|
|
**Manuell:**
|
|
|
|
```bash
|
|
# 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:**
|
|
|
|
```bash
|
|
# 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 `DhlShipment` erweitert 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 `recipient` gespeichert
|
|
|
|
**Zweck der Felder:**
|
|
|
|
- `email`: Wird für DHL Benachrichtigungen und Tracking-E-Mails verwendet
|
|
- `postnumber`: DHL Postnummer (6-10 Stellen) für Packstation/Paketbox-Lieferungen
|
|
|
|
**E-Mail-Button-Logik:**
|
|
|
|
- ✅ Button wird angezeigt, wenn Sendung eine `dhl_shipment_no` hat UND eine E-Mail verfügbar ist
|
|
- ✅ Priorisierung: Shipment-Email > Shopping-User-Email
|
|
- ✅ `canSendTrackingEmail()` prüft zuerst das neue `email` Feld
|
|
- ✅ Fallback auf `shopping_user->email` wenn Shipment-Email leer
|
|
- ✅ Button funktioniert in beiden Views: Bestelldetails + DHL Cockpit
|
|
|
|
**E-Mail-Versand-Priorisierung:**
|
|
|
|
1. **Test-E-Mail** (falls angegeben im Cronjob mit `--test-email`)
|
|
2. **Shipment-Email** (aus `dhl_package_shipments.email`)
|
|
3. **Shopping-User-Email** (Fallback aus `shopping_users.email`)
|
|
|
|
**Betroffene Dateien (E-Mail + Postnummer):**
|
|
|
|
1. `database/migrations/2026_01_23_140000_add_email_and_postnumber_to_dhl_shipments.php` - NEU
|
|
2. `packages/acme-laravel-dhl/src/Models/DhlShipment.php` - fillable + canSendTrackingEmail() erweitert
|
|
3. `resources/views/admin/dhl/modal_in_order_shipment.blade.php` - E-Mail-Feld hinzugefügt
|
|
4. `app/Http/Controllers/DhlShipmentController.php` - Validierung + E-Mail-Priorisierung
|
|
5. `packages/acme-laravel-dhl/src/Services/ShippingService.php` - Speicherung erweitert
|
|
6. `app/Console/Commands/DhlUpdateTracking.php` - E-Mail-Priorisierung im Cronjob
|
|
7. `app/Services/DhlDataHelper.php` - Übergibt E-Mail + Postnummer (bereits vorhanden)
|
|
8. `app/Services/DhlModalService.php` - Liest Formularfelder (bereits vorhanden)
|
|
9. `resources/views/admin/dhl/show.blade.php` - E-Mail-Button + JavaScript Handler
|
|
10. `resources/views/admin/sales/_detail_dhl_shipments.blade.php` - E-Mail-Button + Handler (bereits vorhanden)
|
|
11. `resources/views/admin/dhl/modal_in_shipment_info.blade.php` - E-Mail-Button im Modal
|
|
12. `app/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):**
|
|
|
|
1. `app/Http/Controllers/DhlShipmentController.php` - DataTable Spalten (ID, Typ)
|
|
2. `resources/views/admin/dhl/cockpit.blade.php` - CSS + JS für Zeilen-Highlighting
|
|
3. `resources/views/admin/dhl/show.blade.php` - Header Badge + Icon
|
|
4. `resources/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:
|
|
|
|
- `paid` kam zuerst → Status = 10 (korrekt)
|
|
- `appointed` kam 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:**
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
$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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
1. **OrderPaymentService** (`app/Services/OrderPaymentService.php`)
|
|
|
|
- Neue Methode: `getStatusBadgeClasses()` für zentrale Status-Definition
|
|
- `getStatusBadge()` nutzt jetzt zentrale Definition
|
|
|
|
2. **Payment Links View** (`resources/views/user/order/payment/index.blade.php`)
|
|
|
|
- Dynamische Status-Legende über `@foreach` generiert
|
|
- Mehrsprachige Labels via `__('payment.' . $statusKey)`
|
|
|
|
3. **PayoneController** (`app/Http/Controllers/Api/PayoneController.php`)
|
|
|
|
- Prioritätsprüfung vor `txaction` Update
|
|
- Logging bei übersprungenen Updates
|
|
|
|
4. **Payment Service** (`app/Services/Payment.php`)
|
|
|
|
- Status 10 wird jetzt für ALLE Bestellungen gesetzt (Zeile 262-267)
|
|
- Nicht mehr nur innerhalb der `is_abo` Bedingung
|
|
|
|
5. **ShoppingInstance Model** (`app/Models/ShoppingInstance.php`)
|
|
|
|
- Primary Key korrekt konfiguriert
|
|
- Laravel Eloquent funktioniert mit identifier-basierter Tabelle
|
|
|
|
6. **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
|
|
|
|
7. **Ü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"
|
|
|
|
**Verwendung:**
|
|
|
|
```bash
|
|
# 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 |
|
|
| ~~7~~ | ~~Stornorechnungen + Punktekorrektur~~ | ~~Hoch~~ | ~~Hoch~~ | ✅ **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 |
|
|
| ~~12~~ | ~~Bezahllink Status~~ | ~~Mittel~~ | ~~Niedrig~~ | ✅ **ERLEDIGT** |
|
|
| 13 | Steuerberater | Niedrig | ? | TBD |
|
|
| 14 | DHL UI + Tracking-Mail | Hoch | Mittel | Package |
|
|
|
|
---
|
|
|
|
## EMPFOHLENE REIHENFOLGE
|
|
|
|
### Phase 1: Quick Wins (Frontend, niedrige Komplexität)
|
|
|
|
- [x] #1 News Links **ERLEDIGT 22.01.2026**
|
|
- [x] #3 Vorkasse TXID Hinweis **ERLEDIGT 22.01.2026**
|
|
- [x] #12 Bezahllink Status ✅ **ERLEDIGT 28.01.2026**
|
|
|
|
### Phase 2: Kritische Bugs (Provisionen betroffen)
|
|
|
|
- [x] #9 Gutschriften Punkte Bug
|
|
- [ ] #10 Nicht zugeordnete Zahlungen
|
|
|
|
### Phase 3: Infrastruktur (DB-Änderungen)
|
|
|
|
- [x] #2 Points DECIMAL (benötigt Migration + Testing) **ERLEDIGT 22.01.2026**
|
|
- [x] #7 Stornorechnungen mit Punktekorrektur ✅ **ERLEDIGT 06.02.2026**
|
|
|
|
### Phase 4: Features
|
|
|
|
- [x] #6 Mehrsprachigkeit PDFs
|
|
- [x] #14 DHL UI + Tracking-Mail **ERLEDIGT 23.01.2026**
|
|
- [ ] #11 Monatsstatistik Erweiterungen
|
|
|
|
### Phase 5: Langfristig
|
|
|
|
- [x] #4 Packstation/Postnummer **ERLEDIGT 22.01.2026**
|
|
- [x] #5 Set-Produkte Bundles **ERLEDIGT 22.01.2026**
|
|
- [ ] #8 Französisch
|
|
- [ ] #13 Steuerberater-Modul
|