Statistiken/Abo: Kundenfeedback 2026-07-24 komplett umgesetzt + Steuern: DATEV-Fixes 08+09/2025 (Sammel-Commit Branch-WIP)

Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert

Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-07-24 13:51:16 +00:00
parent c06740b9be
commit d4e9d108ad
137 changed files with 7610 additions and 2955 deletions

View file

@ -7,15 +7,15 @@ use App\Models\Incentive;
use App\Models\IncentiveParticipant;
use App\Models\ShoppingPayment;
use App\Models\UserAbo;
use App\Models\UserBusiness;
use App\Services\Abo\PaylineForecastService;
use App\Services\Abo\TeamAboOverviewService;
use App\Services\AboHelper;
use App\Services\Backoffice\BackofficeDashboardService;
use App\Services\BusinessPlan\TreeCalcBot;
use App\Services\BusinessPlan\LiveCommissionService;
use App\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
use Request;
use Util;
@ -38,11 +38,11 @@ class HomeController extends Controller
return redirect('home');
}
public function newsArchive(): \Illuminate\View\View
public function newsArchive(): View
{
return view('dashboard.news_archive', [
'currentNews' => \App\Models\DashboardNews::getActiveNews(),
'archiveNews' => \App\Models\DashboardNews::getArchiveNews(),
'currentNews' => DashboardNews::getActiveNews(),
'archiveNews' => DashboardNews::getArchiveNews(),
]);
}
@ -111,10 +111,18 @@ class HomeController extends Controller
private function buildMonthlyStats(User $user): array
{
$month = max(1, min(12, (int) Request::get('stats_month', now()->month)));
$year = (int) Request::get('stats_year', now()->year);
$year = max(2020, min((int) now()->year + 1, (int) Request::get('stats_year', now()->year)));
$salesVolume = $user->getUserSalesVolume($month, $year, 'first');
[$paylinePoints, $paylineIsLive] = $this->teamPaylinePoints($user, $month, $year);
$customerPoints = $salesVolume ? (float) $salesVolume->getPointsKPSum() : 0.0;
// Payline-Prognose (B10) nur für den laufenden Monat — Basis ist der heutige
// Stand aus Kundenumsatz + Payline-Punkten.
$isCurrentMonth = $month === (int) now()->month && $year === (int) now()->year;
$paylineForecast = $isCurrentMonth
? app(PaylineForecastService::class)->forecastDays($user, $customerPoints + (float) $paylinePoints)
: null;
$firstlineIds = User::where('m_sponsor', $user->id)
->whereColumn('id', '!=', 'm_sponsor')
@ -130,9 +138,11 @@ class HomeController extends Controller
[
'month' => $month,
'year' => $year,
'customer_points' => $salesVolume ? (float) $salesVolume->getPointsKPSum() : 0.0,
'customer_points' => $customerPoints,
'team_payline_points' => $paylinePoints,
'payline_is_live' => $paylineIsLive,
'payline_forecast' => $paylineForecast,
'commission' => app(LiveCommissionService::class)->commission($user, $month, $year),
'direct_new_partners' => $dashboardService->newPartnersCount($firstlineIds, $month, $year),
],
$teamOverview->teamKpis($user, $month, $year),
@ -141,45 +151,16 @@ class HomeController extends Controller
}
/**
* Team-Payline-Punkte: aus dem UserBusiness-Snapshot (abgeschlossene Monate) oder
* – für den laufenden Monat – einmalig live berechnet und für Folge-Aufrufe gecacht.
* Team-Payline-Punkte: aus dem gemeinsamen Live-Lauf ({@see LiveCommissionService}),
* der auch die Provision und die Payline-Tiefe liefert — ein Baum-Lauf für alles.
*
* @return array{0: float, 1: bool} [Punkte, istLive]
*/
private function teamPaylinePoints(User $user, int $month, int $year): array
{
$userBusiness = UserBusiness::where('user_id', $user->id)
->where('month', $month)
->where('year', $year)
->first();
$snapshot = app(LiveCommissionService::class)->snapshot($user, $month, $year);
if ($userBusiness) {
return [(float) ($userBusiness->payline_points ?? 0), false];
}
$points = Cache::remember(
"home_payline_{$user->id}_{$month}_{$year}",
now()->addHours(6),
fn (): float => $this->calculatePaylineLive($user, $month, $year)
);
return [(float) $points, true];
}
private function calculatePaylineLive(User $user, int $month, int $year): float
{
try {
$treeCalcBot = new TreeCalcBot($month, $year, 'member');
$treeCalcBot->initBusinesslUserDetail($user);
if ($treeCalcBot->business_user) {
return (float) ($treeCalcBot->business_user->payline_points ?? 0);
}
} catch (\Throwable $e) {
\Log::warning("Live-Berechnung Payline-Punkte fehlgeschlagen für User {$user->id}: ".$e->getMessage());
}
return 0.0;
return [$snapshot['payline_points'], $snapshot['is_live']];
}
public function loadingModal()