count(); $today = Carbon::today(); $present = Attendance::query() ->where('attendance_date', $today) ->distinct('employee_id') ->count('employee_id'); $onLeave = LeaveRequest::query() ->approved() ->where('start_date', '<=', $today) ->where('end_date', '>=', $today) ->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 { $totalBalance = CashAccount::query()->sum('balance'); $summary = CashTransaction::query() ->whereDate('created_at', Carbon::today()) ->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 { return Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->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 { return Order::query() ->completed() ->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 { $revenueSummary = Order::query() ->completed() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, SUM(shipping_cost) as total_shipping, COUNT(*) as total_orders, AVG(total_amount) as avg_order') ->first(); $totalMarketplaceFees = Order::query() ->completed() ->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)); return [ 'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0), 'total_discount' => (int) ($revenueSummary->total_discount ?? 0), 'total_marketplace_fees' => $totalMarketplaceFees, 'total_potongan' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees, 'total_shipping' => (int) ($revenueSummary->total_shipping ?? 0), 'total_orders' => (int) ($revenueSummary->total_orders ?? 0), 'avg_order' => (int) ($revenueSummary->avg_order ?? 0), ]; } public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array { $query = Order::query()->completed(); $feeQuery = Order::query()->completed()->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, SUM(shipping_cost) as total_shipping ") ->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))); 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); $potongan = $discount + $fees; $result[] = [ 'month' => $monthLabel, 'total' => (int) ($revenue->total_revenue ?? 0), 'net' => (int) ($revenue->total_revenue ?? 0) - $potongan, 'potongan' => $potongan, 'ongkir' => (int) ($revenue->total_shipping ?? 0), ]; $current->addMonth(); } return $result; } public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array { $purchase = Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(total), 0) as total') ->first(); $expenses = Expense::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(amount), 0) as total') ->first(); $employeeAdvance = EmployeeAdvance::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(amount), 0) as total') ->first(); $purchaseTotal = (int) ($purchase->total ?? 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 { $purchases = Purchase::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate])) ->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])) ->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() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->selectRaw("DATE_FORMAT(employee_advances.created_at, '%Y-%m') as month_key, COALESCE(SUM(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); $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, 'belanja' => $purchaseAmount, 'pengeluaran' => $expenseAmount, 'kasbon' => $advanceAmount, ]; $current->addMonth(); } return $result; } public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array { $orderQuery = Order::query()->completed(); 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($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 cutting_result_prices $hpp = OrderItem::query() ->join('orders', 'order_items.order_id', '=', 'orders.id') ->leftJoin('cutting_result_prices', 'order_items.product_variant_id', '=', 'cutting_result_prices.product_variant_id') ->where('orders.status', OrderStatus::COMPLETED) ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate])) ->selectRaw('COALESCE(SUM(order_items.quantity * cutting_result_prices.cost_per_unit), 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])) ->sum('total'); $expenseTotal = Expense::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate])) ->sum('amount'); $advanceTotal = EmployeeAdvance::query() ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate])) ->sum('amount'); $totalExpenses = (int) $purchaseTotal + (int) $expenseTotal + (int) $advanceTotal; $labaKotor = $totalRevenue - $totalHpp - $totalDiscount - $marketplaceFees; $labaBersih = $labaKotor - $totalExpenses; $profitMargin = $totalRevenue > 0 ? round(($labaBersih / $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, 'laba_kotor' => $labaKotor, 'laba_bersih' => $labaBersih, '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, 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') ->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_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 { $hourlyData = Order::query() ->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 { 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($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(); } }