user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $employeeQuery = Employee::query(); $attendanceQuery = Attendance::query()->where('attendance_date', Carbon::today()); $leaveRequestQuery = LeaveRequest::query() ->approved() ->where('start_date', '<=', Carbon::today()) ->where('end_date', '>=', Carbon::today()); if (! $isSuper) { $employeeId = $user?->employee?->id; $employeeQuery->where('id', $employeeId); $attendanceQuery->where('employee_id', $employeeId); $leaveRequestQuery->where('employee_id', $employeeId); } $totalEmployees = $employeeQuery->count(); $present = $attendanceQuery->distinct('employee_id')->count('employee_id'); $onLeave = $leaveRequestQuery->distinct('employee_id')->count('employee_id'); $absent = max(0, $totalEmployees - $present - $onLeave); return [ 'total_employees' => $totalEmployees, 'percentage' => $totalEmployees > 0 ? round($present / $totalEmployees * 100) : 0, 'present' => $present, 'absent' => $absent, 'on_leave' => $onLeave, ]; } public function getMyAttendance(User $user, ?Carbon $startDate = null, ?Carbon $endDate = null): array { $employee = $user->employee; if ($employee === null) { return [ 'total_days' => 0, 'present_days' => 0, 'absent_days' => 0, 'leave_days' => 0, 'percentage' => 0, ]; } $start = $startDate ?? ($employee->join_date ? Carbon::parse($employee->join_date) : Carbon::today()->startOfMonth()); $end = $endDate ?? Carbon::today(); $totalDays = 0; $presentDays = 0; $current = $start->copy()->startOfDay(); while ($current->lte($end)) { if ($current->isWeekday()) { $totalDays++; $hasAttendance = Attendance::query() ->where('employee_id', $employee->id) ->whereDate('attendance_date', $current) ->exists(); if ($hasAttendance) { $presentDays++; } } $current->addDay(); } $leaveDays = LeaveRequest::query() ->approved() ->where('employee_id', $employee->id) ->where('start_date', '<=', $end->toDateString()) ->where('end_date', '>=', $start->toDateString()) ->get() ->sum(fn ($leave) => max( 0, min($leave->end_date, $end->toDateString()) - max($leave->start_date, $start->toDateString()) ) / 86400 + 1); $leaveDays = (int) $leaveDays; $absentDays = max(0, $totalDays - $presentDays - $leaveDays); $percentage = $totalDays > 0 ? round(($presentDays / $totalDays) * 100) : 0; return [ 'total_days' => $totalDays, 'present_days' => $presentDays, 'absent_days' => $absentDays, 'leave_days' => $leaveDays, 'percentage' => $percentage, ]; } public function getCashOverview(): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $totalBalanceQuery = CashAccount::query(); $transactionQuery = CashTransaction::query()->whereDate('created_at', Carbon::today()); if (! $isSuper) { $transactionQuery->where('created_by_id', $user->id); $totalBalanceQuery->whereHas('transactions', fn ($q) => $q->where('created_by_id', $user->id)); } $totalBalance = $totalBalanceQuery->sum('balance'); $summary = $transactionQuery ->selectRaw(" COUNT(*) as total_transactions, COALESCE(SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END), 0) as total_deposit, COALESCE(SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END), 0) as total_withdrawal ") ->first(); return [ 'total_balance' => (int) $totalBalance, 'total_transactions' => (int) ($summary->total_transactions ?? 0), 'total_deposit' => (int) ($summary->total_deposit ?? 0), 'total_withdrawal' => (int) ($summary->total_withdrawal ?? 0), ]; } public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; return Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('purchases.created_by_id', $user->id)) ->join('suppliers', 'purchases.supplier_id', '=', 'suppliers.id') ->selectRaw('suppliers.name, SUM(purchases.total) as total_amount, COUNT(purchases.id) as purchase_count') ->groupBy('suppliers.id', 'suppliers.name') ->orderByDesc('total_amount') ->limit(5) ->get() ->map(fn ($item) => [ 'name' => $item->name, 'total_amount' => (int) $item->total_amount, 'purchase_count' => (int) $item->purchase_count, ]) ->toArray(); } public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; return Order::query() ->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->join('customers', 'orders.customer_id', '=', 'customers.id') ->selectRaw('customers.name, SUM(orders.total_amount) as total_amount, COUNT(orders.id) as order_count') ->groupBy('customers.id', 'customers.name') ->orderByDesc('total_amount') ->limit(5) ->get() ->map(fn ($item) => [ 'name' => $item->name, 'total_amount' => (int) $item->total_amount, 'order_count' => (int) $item->order_count, ]) ->toArray(); } public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; $revenueSummary = Order::query() ->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, COUNT(*) as total_orders, AVG(total_amount) as avg_order') ->first(); $totalMarketplaceFees = Order::query() ->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)) ->whereNotNull('marketplace_settings_snapshot') ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->get() ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); $totalCostPrice = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->leftJoin('product_prices', function ($join) { $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') ->where('product_prices.type', PriceType::HARGA_MODAL->value); }) ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(order_items.quantity * product_prices.price), 0) as total_hpp') ->value('total_hpp'); return [ 'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0), 'total_discount' => (int) ($revenueSummary->total_discount ?? 0), 'total_marketplace_fees' => $totalMarketplaceFees, 'total_cost_price' => (int) ($totalCostPrice ?? 0), 'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees, 'total_orders' => (int) ($revenueSummary->total_orders ?? 0), 'avg_order' => (int) ($revenueSummary->avg_order ?? 0), ]; } public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; $query = Order::query()->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)); $feeQuery = Order::query()->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)) ->whereNotNull('marketplace_settings_snapshot'); if ($startDate && $endDate) { $query->whereBetween('orders.created_at', [$startDate, $endDate]); $feeQuery->whereBetween('orders.created_at', [$startDate, $endDate]); } $monthlyData = $query ->selectRaw(" DATE_FORMAT(orders.created_at, '%Y-%m') as month_key, SUM(total_amount) as total_revenue, SUM(discount) as total_discount ") ->groupBy('month_key') ->orderBy('month_key') ->get(); $monthlyFees = $feeQuery ->selectRaw("DATE_FORMAT(orders.created_at, '%Y-%m') as month_key") ->get() ->groupBy('month_key') ->map(fn ($orders) => $orders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0))); $monthlyItemsData = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->leftJoin('product_prices', function ($join) { $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') ->where('product_prices.type', PriceType::HARGA_MODAL->value); }) ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw(" DATE_FORMAT(orders.created_at, '%Y-%m') as month_key, order_items.stock_quality, SUM(order_items.subtotal) as subtotal, COALESCE(SUM(order_items.quantity * product_prices.price), 0) as hpp ") ->groupBy('month_key', 'order_items.stock_quality') ->get() ->groupBy('month_key'); if ($monthlyData->isEmpty()) { return []; } $start = $startDate ?? Carbon::parse($monthlyData->first()->month_key.'-01'); $end = $endDate ?? Carbon::parse($monthlyData->last()->month_key.'-01')->endOfMonth(); $result = []; $current = $start->copy()->startOfMonth(); while ($current->lte($end)) { $key = $current->format('Y-m'); $monthLabel = $current->locale('id')->translatedFormat('M Y'); $revenue = $monthlyData->firstWhere('month_key', $key); $fees = $monthlyFees->get($key, 0); $discount = (int) ($revenue->total_discount ?? 0); $deduction = $discount + $fees; $monthItems = $monthlyItemsData->get($key, collect()); $warehouseItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'good' ); $retailItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'retail' ); $rejectItem = $monthItems->first(fn ($item) => ($item->stock_quality instanceof ProductStockQuality ? $item->stock_quality->value : $item->stock_quality) === 'reject' ); $warehouseSubtotal = (int) ($warehouseItem?->subtotal ?? 0); $warehouseHpp = (int) ($warehouseItem?->hpp ?? 0); $retailSubtotal = (int) ($retailItem?->subtotal ?? 0); $retailHpp = (int) ($retailItem?->hpp ?? 0); $rejectSubtotal = (int) ($rejectItem?->subtotal ?? 0); $rejectHpp = (int) ($rejectItem?->hpp ?? 0); $totalItemsSubtotal = $warehouseSubtotal + $retailSubtotal + $rejectSubtotal; $hpp = $warehouseHpp + $retailHpp + $rejectHpp; $warehouseDeduction = 0; $retailDeduction = 0; if ($totalItemsSubtotal > 0) { $warehouseDeduction = ($warehouseSubtotal / $totalItemsSubtotal) * $deduction; $retailDeduction = ($retailSubtotal / $totalItemsSubtotal) * $deduction; } $netWarehouse = $warehouseSubtotal - $warehouseDeduction - $warehouseHpp; $netRetail = $retailSubtotal - $retailDeduction - $retailHpp; $result[] = [ 'month' => $monthLabel, 'total' => (int) ($revenue->total_revenue ?? 0), 'net' => (int) ($revenue->total_revenue ?? 0) - $deduction - $hpp, 'net_warehouse' => (int) round($netWarehouse), 'net_retail' => (int) round($netRetail), 'deduction' => $deduction, ]; $current->addMonth(); } return $result; } public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $purchase = Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('purchases.created_by_id', $user->id)) ->selectRaw('COALESCE(SUM(total), 0) as total') ->first(); $expenses = Expense::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('expenses.created_by_id', $user->id)) ->selectRaw('COALESCE(SUM(amount), 0) as total') ->first(); $employeeAdvance = EmployeeAdvance::query() ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID]) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('employee_id', $user->employee?->id)) ->selectRaw('COALESCE(SUM(amount - paid_amount), 0) as total') ->first(); $purchaseTotal = (int) ($purchase->total ?? 0); if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseTotal = 0; } $expenseTotal = (int) ($expenses->total ?? 0); $advanceTotal = (int) ($employeeAdvance->total ?? 0); return [ 'total' => $purchaseTotal + $expenseTotal + $advanceTotal, 'purchase_total' => $purchaseTotal, 'expense_total' => $expenseTotal, 'advance_total' => $advanceTotal, ]; } public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $purchases = Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('purchases.created_by_id', $user->id)) ->selectRaw("DATE_FORMAT(purchases.created_at, '%Y-%m') as month_key, COALESCE(SUM(total), 0) as total") ->groupBy('month_key') ->orderBy('month_key') ->get(); $expenses = Expense::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('expenses.created_by_id', $user->id)) ->selectRaw("DATE_FORMAT(expenses.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount), 0) as total") ->groupBy('month_key') ->orderBy('month_key') ->get(); $advances = EmployeeAdvance::query() ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID]) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('employee_id', $user->employee?->id)) ->selectRaw("DATE_FORMAT(employee_advances.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount - paid_amount), 0) as total") ->groupBy('month_key') ->orderBy('month_key') ->get(); $allMonths = collect() ->merge($purchases->pluck('month_key')) ->merge($expenses->pluck('month_key')) ->merge($advances->pluck('month_key')) ->unique() ->sort() ->values(); if ($allMonths->isEmpty()) { return []; } $start = $startDate ?? Carbon::parse($allMonths->first().'-01'); $end = $endDate ?? Carbon::parse($allMonths->last().'-01')->endOfMonth(); $result = []; $current = $start->copy()->startOfMonth(); while ($current->lte($end)) { $key = $current->format('Y-m'); $monthLabel = $current->locale('id')->translatedFormat('M Y'); $purchaseAmount = (int) ($purchases->firstWhere('month_key', $key)->total ?? 0); if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseAmount = 0; } $expenseAmount = (int) ($expenses->firstWhere('month_key', $key)->total ?? 0); $advanceAmount = (int) ($advances->firstWhere('month_key', $key)->total ?? 0); $result[] = [ 'month' => $monthLabel, 'total' => $purchaseAmount + $expenseAmount + $advanceAmount, 'purchase' => $purchaseAmount, 'expense' => $expenseAmount, 'advance' => $advanceAmount, ]; $current->addMonth(); } return $result; } public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; $orderQuery = Order::query()->completed() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)); if ($startDate && $endDate) { $orderQuery->whereBetween('orders.created_at', [$startDate, $endDate]); } $revenueData = (clone $orderQuery) ->selectRaw(' COUNT(*) as total_orders, SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(subtotal) as total_subtotal ') ->first(); $totalRevenue = (int) ($revenueData->total_revenue ?? 0); $totalDiscount = (int) ($revenueData->total_discount ?? 0); $totalSubtotal = (int) ($revenueData->total_subtotal ?? 0); $totalOrders = (int) ($revenueData->total_orders ?? 0); $marketplaceFees = (clone $orderQuery) ->whereNotNull('marketplace_settings_snapshot') ->get() ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)); $itemsData = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw(' SUM(order_items.quantity) as total_qty, COUNT(order_items.id) as total_items ') ->first(); $totalQty = (int) ($itemsData->total_qty ?? 0); $totalItems = (int) ($itemsData->total_items ?? 0); // HPP from product_prices with type = 'harga_modal' $hpp = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->leftJoin('product_prices', function ($join) { $join->on('order_items.product_variant_id', '=', 'product_prices.variant_id') ->where('product_prices.type', PriceType::HARGA_MODAL->value); }) ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(order_items.quantity * product_prices.price), 0) as total_hpp') ->value('total_hpp'); $totalHpp = (int) ($hpp ?? 0); // Expenses $purchaseTotal = Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('purchases.created_by_id', $user->id)) ->sum('total'); if (! $user?->hasAnyRole([Role::DEVELOPER->value, Role::OWNER->value])) { $purchaseTotal = 0; } $expenseTotal = Expense::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('expenses.created_by_id', $user->id)) ->sum('amount'); $advanceTotal = EmployeeAdvance::query() ->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID]) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->when(! $isSuper, fn ($q) => $q->where('employee_id', $user->employee?->id)) ->sum(DB::raw('amount - paid_amount')); $totalExpenses = (int) $purchaseTotal + (int) $expenseTotal + (int) $advanceTotal; $grossProfit = $totalRevenue - $totalHpp - $totalDiscount - $marketplaceFees; $netProfit = $grossProfit - (int) $expenseTotal - (int) $advanceTotal; $profitMargin = $totalRevenue > 0 ? round(($netProfit / $totalRevenue) * 100, 1) : 0; $aov = $totalOrders > 0 ? (int) round($totalRevenue / $totalOrders) : 0; $itemsPerTransaction = $totalOrders > 0 ? round($totalItems / $totalOrders, 1) : 0; return [ 'total_orders' => $totalOrders, 'total_products_sold' => $totalQty, 'hpp' => $totalHpp, 'gross_profit' => $grossProfit, 'net_profit' => $netProfit, 'profit_margin' => $profitMargin, 'aov' => $aov, 'items_per_transaction' => $itemsPerTransaction, ]; } public function getRawMaterialStock(): array { $prices = RawMaterialPrice::query() ->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id') ->selectRaw(' raw_materials.unit, SUM(raw_material_prices.stock) as total_stock, SUM(raw_material_prices.stock * raw_material_prices.price) as total_value ') ->groupBy('raw_materials.unit') ->get() ->keyBy('unit'); $totalStock = (float) $prices->sum('total_stock'); $totalValue = (int) $prices->sum('total_value'); return [ 'total_stock' => round($totalStock, 2), 'total_value' => $totalValue, 'by_unit' => [ 'yard' => round((float) ($prices->get('yard')->total_stock ?? 0), 2), 'meter' => round((float) ($prices->get('meter')->total_stock ?? 0), 2), 'kilogram' => round((float) ($prices->get('kilogram')->total_stock ?? 0), 2), ], ]; } public function getProductStock(): array { $variants = ProductVariant::query() ->join('products', 'product_variants.product_id', '=', 'products.id') ->selectRaw(' SUM(product_variants.stock) as total_stock, SUM(product_variants.reject_stock) as total_reject, SUM(product_variants.retail_stock) as total_retail, COUNT(product_variants.id) as total_variants, COUNT(DISTINCT products.id) as total_products ') ->first(); $totalValue = \DB::table('product_prices') ->join('product_variants', 'product_prices.variant_id', '=', 'product_variants.id') ->where('product_prices.type', PriceType::HARGA_MODAL->value) ->whereNull('product_variants.deleted_at') ->whereNull('product_prices.deleted_at') ->selectRaw('SUM(product_variants.stock * product_prices.price) as total') ->value('total'); $totalCategories = \DB::table('product_categories') ->distinct('category_id') ->count('category_id'); return [ 'total_stock' => (int) ($variants->total_stock ?? 0), 'total_reject' => (int) ($variants->total_reject ?? 0), 'total_retail' => (int) ($variants->total_retail ?? 0), 'total_value' => (int) ($totalValue ?? 0), 'total_products' => (int) ($variants->total_products ?? 0), 'total_variants' => (int) ($variants->total_variants ?? 0), 'total_categories' => (int) $totalCategories, ]; } public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; $hourlyData = Order::query() ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count') ->groupBy('hour') ->orderBy('hour') ->get() ->keyBy('hour'); $result = []; for ($h = 0; $h < 24; $h++) { $result[] = [ 'hour' => str_pad($h, 2, '0', STR_PAD_LEFT).':00', 'orders' => (int) ($hourlyData->get($h)->order_count ?? 0), ]; } return $result; } public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = null): array { /** @var User|null $user */ $user = auth()->user(); $isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false; $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false; return OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id') ->join('products', 'product_variants.product_id', '=', 'products.id') ->where('orders.status', OrderStatus::COMPLETED) ->when(! $isSuper && $isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id)) ->when(! $isSuper && ! $isMarketing, fn ($q) => $q->where('orders.created_by_id', $user->id)) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw("CONCAT(products.name, ' - ', product_variants.name) as full_name, SUM(order_items.quantity) as total_qty, SUM(order_items.subtotal) as total_revenue") ->groupBy('order_items.product_variant_id', 'products.name', 'product_variants.name') ->orderByDesc('total_qty') ->limit(5) ->get() ->map(fn ($item) => [ 'name' => $item->full_name, 'total_qty' => (int) $item->total_qty, 'total_revenue' => (int) $item->total_revenue, ]) ->toArray(); } public function getMarketingSales(?Carbon $startDate = null, ?Carbon $endDate = null): array { $qtySubquery = DB::table('order_items') ->select('order_id', DB::raw('SUM(quantity) as total_qty')) ->whereNull('deleted_at') ->groupBy('order_id'); return Order::query() ->completed() ->whereNotNull('marketing_id') ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->join('users', 'orders.marketing_id', '=', 'users.id') ->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id') ->leftJoinSub($qtySubquery, 'order_qtys', function ($join) { $join->on('orders.id', '=', 'order_qtys.order_id'); }) ->selectRaw(' users.id as marketing_id, COALESCE(user_profiles.full_name, users.username) as marketing_name, COUNT(orders.id) as total_orders, SUM(orders.total_amount) as total_revenue, SUM(orders.subtotal) as total_subtotal, SUM(orders.discount) as total_discount, AVG(orders.total_amount) as avg_order, SUM(COALESCE(order_qtys.total_qty, 0)) as total_products_sold ') ->groupBy('users.id', 'user_profiles.full_name', 'users.username') ->orderByDesc('total_revenue') ->get() ->map(fn ($item) => [ 'marketing_name' => $item->marketing_name, 'total_orders' => (int) $item->total_orders, 'total_products_sold' => (int) $item->total_products_sold, 'total_revenue' => (int) $item->total_revenue, 'total_subtotal' => (int) $item->total_subtotal, 'total_discount' => (int) $item->total_discount, 'avg_order' => (int) $item->avg_order, ]) ->toArray(); } public function isManager(?User $user): bool { return $user?->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]) ?? false; } }