tab = $tab; } public function setPeriod(string $p): void { $this->period = $p; } public function tabs(): array { return [ 'overview' => '📊 Overview', 'cashflow' => '💸 Cashflow', 'pnl' => '📈 P&L', 'balance' => '⚖️ Balanță', ]; } public function periods(): array { return [ 'this_month' => 'Luna curentă', 'last_month' => 'Luna trecută', 'this_year' => 'Anul curent', 'last_30' => 'Ultimele 30 zile', ]; } public function dateRange(): array { return match ($this->period) { 'last_month' => [Carbon::now()->subMonthNoOverflow()->startOfMonth(), Carbon::now()->subMonthNoOverflow()->endOfMonth()], 'this_year' => [Carbon::now()->startOfYear(), Carbon::now()->endOfYear()], 'last_30' => [Carbon::now()->subDays(30)->startOfDay(), Carbon::now()->endOfDay()], default => [Carbon::now()->startOfMonth(), Carbon::now()->endOfMonth()], }; } public function data(): array { [$start, $end] = $this->dateRange(); return match ($this->tab) { 'overview' => $this->overview($start, $end), 'cashflow' => $this->cashflow($start, $end), 'pnl' => $this->pnl($start, $end), 'balance' => $this->balance($start, $end), default => [], }; } protected function overview($start, $end): array { $income = (float) Payment::whereBetween('paid_at', [$start, $end])->sum('amount'); $expenses = (float) Expense::whereBetween('paid_at', [$start, $end])->sum('amount'); $profit = $income - $expenses; $debtTotal = (float) WorkOrder::where('pay_status', '!=', 'paid') ->whereNotIn('status', ['cancelled']) ->get() ->sum(fn ($w) => max(0, (float) $w->total - (float) $w->payments()->sum('amount'))); return compact('income', 'expenses', 'profit', 'debtTotal'); } protected function cashflow($start, $end): array { // Aggregate per day. $byDay = []; $cur = $start->copy(); while ($cur <= $end) { $key = $cur->format('Y-m-d'); $byDay[$key] = ['date' => $cur->format('d.m'), 'in' => 0, 'out' => 0]; $cur->addDay(); } Payment::whereBetween('paid_at', [$start, $end]) ->selectRaw('DATE(paid_at) d, SUM(amount) total') ->groupBy('d')->get() ->each(fn ($r) => $byDay[$r->d]['in'] = (float) $r->total); Expense::whereBetween('paid_at', [$start, $end]) ->selectRaw('DATE(paid_at) d, SUM(amount) total') ->groupBy('d')->get() ->each(fn ($r) => $byDay[$r->d]['out'] = (float) $r->total); // Totals + max (for bar chart scale). $totalIn = array_sum(array_column($byDay, 'in')); $totalOut = array_sum(array_column($byDay, 'out')); $maxAbs = max(0.001, max(...array_map(fn ($d) => max($d['in'], $d['out']), $byDay))); return [ 'rows' => array_values($byDay), 'totalIn' => $totalIn, 'totalOut' => $totalOut, 'net' => $totalIn - $totalOut, 'maxAbs' => $maxAbs, ]; } protected function pnl($start, $end): array { // Revenue split: works vs parts vs other (manually entered payments without WO link). $worksRevenue = (float) WorkOrderWork::whereHas('workOrder', fn ($q) => $q->whereBetween('opened_at', [$start, $end])) ->where('status', 'done') ->sum('total'); $partsRevenue = (float) WorkOrderPart::whereHas('workOrder', fn ($q) => $q->whereBetween('opened_at', [$start, $end])) ->where('status', 'installed') ->sum('total'); $partsCost = (float) WorkOrderPart::whereHas('workOrder', fn ($q) => $q->whereBetween('opened_at', [$start, $end])) ->where('status', 'installed') ->get()->sum(fn ($p) => (float) $p->buy_price * (float) $p->qty); $partsMargin = $partsRevenue - $partsCost; $expensesByCat = Expense::whereBetween('paid_at', [$start, $end]) ->selectRaw('category, SUM(amount) total') ->groupBy('category')->orderByDesc('total')->get(); $totalExpenses = (float) $expensesByCat->sum('total'); $totalRevenue = $worksRevenue + $partsRevenue; $netProfit = $totalRevenue - $partsCost - $totalExpenses; $marginPct = $totalRevenue > 0 ? round($netProfit / $totalRevenue * 100, 1) : 0; return compact( 'worksRevenue', 'partsRevenue', 'partsCost', 'partsMargin', 'expensesByCat', 'totalExpenses', 'totalRevenue', 'netProfit', 'marginPct' ); } protected function balance($start, $end): array { $allPayments = (float) Payment::sum('amount'); $allExpenses = (float) Expense::sum('amount'); $debt = (float) WorkOrder::where('pay_status', '!=', 'paid') ->whereNotIn('status', ['cancelled']) ->get() ->sum(fn ($w) => max(0, (float) $w->total - (float) $w->payments()->sum('amount'))); $stockValue = (float) \App\Models\Tenant\Part::where('is_active', true) ->get() ->sum(fn ($p) => (float) $p->qty * (float) $p->buy_price); return [ 'allTimePayments' => $allPayments, 'allTimeExpenses' => $allExpenses, 'debt' => $debt, 'stockValue' => $stockValue, 'netCash' => $allPayments - $allExpenses, ]; } }