dstpabuaran.com/app/Services/DashboardService.php
Yoga Pangestu c6a87a64c9 feat: enhance dashboard with detailed statistics and charts
- Refactored the dashboard component to include attendance, revenue, and expense summaries.
- Added donut charts for order statistics by channel, payment type, marketing, and status.
- Implemented a greeting message based on the current time and user information.
- Updated routing to use DashboardController for the dashboard view.
- Introduced a new AnalysisController for future analysis features.
2026-08-07 08:35:01 +07:00

209 lines
7.3 KiB
PHP

<?php
namespace App\Services;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentType;
use App\Models\Attendance;
use App\Models\Employee;
use App\Models\EmployeeAdvance;
use App\Models\Expense;
use App\Models\LeaveRequest;
use App\Models\Order;
use App\Models\User;
use Carbon\Carbon;
class DashboardService
{
public function getAttendanceStats(): array
{
$now = Carbon::now();
$startOfMonth = $now->copy()->startOfMonth();
$endOfMonth = $now->copy()->endOfMonth();
$workingDays = 0;
$current = $startOfMonth->copy();
while ($current->lte($endOfMonth)) {
if (! in_array($current->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY])) {
$workingDays++;
}
$current->addDay();
}
$totalEmployees = Employee::whereHas('user', fn ($q) => $q->where('is_active', true))->count();
$present = Attendance::whereYear('attendance_date', $now->year)
->whereMonth('attendance_date', $now->month)
->count();
$leaveDays = LeaveRequest::approved()
->where('start_date', '<=', $endOfMonth)
->where('end_date', '>=', $startOfMonth)
->get()
->reduce(function ($carry, $leave) use ($startOfMonth, $endOfMonth) {
$leaveStart = max($leave->start_date->timestamp, $startOfMonth->timestamp);
$leaveEnd = min($leave->end_date->timestamp, $endOfMonth->timestamp);
$days = Carbon::createFromTimestamp($leaveStart)->diffInDays(Carbon::createFromTimestamp($leaveEnd)) + 1;
return $carry + max(0, $days);
}, 0);
$absent = max(0, $workingDays - $present - $leaveDays);
return [
'total_employees' => $totalEmployees,
'present' => $present,
'absent' => $absent,
'on_leave' => $leaveDays,
'percentage' => $workingDays > 0 ? round(($present / $workingDays) * 100) : 0,
];
}
public function getRevenueSummary(): array
{
$stats = Order::where('status', OrderStatus::COMPLETED)
->selectRaw('COUNT(*) as total_orders')
->selectRaw('COALESCE(SUM(total_amount), 0) as total_revenue')
->selectRaw('COALESCE(SUM(discount), 0) as total_discount')
->selectRaw('COALESCE(SUM(total_amount), 0) - COALESCE(SUM(total_amount), 0) as total_marketplace_fees')
->selectRaw('COALESCE(SUM(discount), 0) as total_deduction')
->first();
return [
'total_revenue' => (int) $stats->total_revenue,
'total_discount' => (int) $stats->total_discount,
'total_marketplace_fees' => (int) $stats->total_marketplace_fees,
'total_deduction' => (int) $stats->total_deduction,
'total_orders' => (int) $stats->total_orders,
'avg_order' => $stats->total_orders > 0 ? (int) ($stats->total_revenue / $stats->total_orders) : 0,
];
}
public function getExpenseSummary(): array
{
$expenses = Expense::selectRaw('COALESCE(SUM(amount), 0) as total')->first();
$cashAdvanceTotal = EmployeeAdvance::where('status', 'paid')
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
$expenseTotal = Expense::selectRaw('COALESCE(SUM(amount), 0) as total')->first();
return [
'total' => (int) $expenses->total,
'purchase_total' => 0,
'expense_total' => (int) $expenseTotal->total,
'advance_total' => (int) ($cashAdvanceTotal->total ?? 0),
];
}
public function getOrderStats(): array
{
$baseQuery = Order::where('status', OrderStatus::COMPLETED);
$byChannel = collect(OrderChannel::values())->map(function ($channel) use ($baseQuery) {
$count = (clone $baseQuery)->where('channel', $channel)->count();
$label = OrderChannel::from($channel)->label();
return [
'channel' => $channel,
'label' => $label,
'count' => $count,
'total' => (int) (clone $baseQuery)->where('channel', $channel)->sum('total_amount'),
];
});
$byPaymentType = collect(PaymentType::values())->map(function ($paymentType) use ($baseQuery) {
$count = (clone $baseQuery)->where('payment_type', $paymentType)->count();
$label = PaymentType::from($paymentType)->label();
return [
'payment_type' => $paymentType,
'label' => $label,
'count' => $count,
'total' => (int) (clone $baseQuery)->where('payment_type', $paymentType)->sum('total_amount'),
];
});
$byMarketing = Order::where('status', OrderStatus::COMPLETED)
->whereNotNull('marketing_id')
->select('marketing_id')
->selectRaw('COUNT(*) as count')
->selectRaw('COALESCE(SUM(total_amount), 0) as total')
->groupBy('marketing_id')
->with('marketing:id')
->get()
->map(fn ($item) => [
'name' => $item->marketing?->userProfile->full_name ?? '-',
'count' => $item->count,
'total' => (int) $item->total,
]);
$byStatus = collect(OrderStatus::values())->map(function ($status) use ($baseQuery) {
$count = (clone $baseQuery)->where('status', $status)->count();
$label = OrderStatus::from($status)->label();
return [
'status' => $status,
'label' => $label,
'count' => $count,
];
});
return [
'by_channel' => $byChannel,
'by_payment_type' => $byPaymentType,
'by_marketing' => $byMarketing,
'by_status' => $byStatus,
];
}
public function getTodayAttendance(User $user): ?array
{
$employee = $user->employee;
if (! $employee) {
return null;
}
$attendance = Attendance::where('employee_id', $employee->id)
->where('attendance_date', now()->toDateString())
->first();
if (! $attendance) {
return null;
}
return [
'id' => $attendance->id,
'attendance_date' => $attendance->attendance_date,
'check_in_at' => $attendance->check_in_at,
'check_out_at' => $attendance->check_out_at,
'check_in_photo' => null,
'check_out_photo' => null,
'check_in_latitude' => $attendance->check_in_latitude,
'check_in_longitude' => $attendance->check_in_longitude,
'check_out_latitude' => $attendance->check_out_latitude,
'check_out_longitude' => $attendance->check_out_longitude,
'work_duration_minutes' => $attendance->work_duration_minutes,
];
}
public function isOnLeave(User $user): bool
{
$employee = $user->employee;
if (! $employee) {
return false;
}
return LeaveRequest::approved()
->where('employee_id', $employee->id)
->where('start_date', '<=', now()->toDateString())
->where('end_date', '>=', now()->toDateString())
->exists();
}
}