dstpabuaran.com/app/Services/DashboardService.php

378 lines
12 KiB
PHP

<?php
namespace App\Services;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentType;
use App\Enums\PayrollStatus;
use App\Enums\Role;
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\OrderItem;
use App\Models\Payroll;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class DashboardService
{
public function getAttendanceStats(?User $user = null): array
{
$today = Carbon::now()->toDateString();
if ($user && $this->isMarketingUser($user)) {
$employee = $user->employee;
if (! $employee) {
return [
'total_employees' => 0,
'present' => 0,
'absent' => 0,
'on_leave' => 0,
'percentage' => 0,
];
}
$present = Attendance::where('employee_id', $employee->id)
->where('attendance_date', $today)
->count();
$onLeave = LeaveRequest::approved()
->where('employee_id', $employee->id)
->where('start_date', '<=', $today)
->where('end_date', '>=', $today)
->count();
$isWorking = ! in_array(Carbon::now()->dayOfWeek, [Carbon::SATURDAY, Carbon::SUNDAY]);
$absent = $isWorking && $present === 0 && $onLeave === 0 ? 1 : 0;
return [
'total_employees' => 1,
'present' => $present,
'absent' => $absent,
'on_leave' => $onLeave,
'percentage' => $present > 0 ? 100 : 0,
];
}
$totalEmployees = Employee::whereHas(
'user',
fn ($q) => $q
->where('is_active', true)
->whereHas(
'roles',
fn ($r) => $r
->whereHas(
'permissions',
fn ($p) => $p
->where('name', 'attendances.create')
)
)
)->count();
$present = Attendance::where('attendance_date', $today)
->whereHas(
'employee.user',
fn ($q) => $q
->where('is_active', true)
->whereHas(
'roles',
fn ($r) => $r
->whereHas(
'permissions',
fn ($p) => $p
->where('name', 'attendances.create')
)
)
)->count();
$onLeave = LeaveRequest::approved()
->where('start_date', '<=', $today)
->where('end_date', '>=', $today)
->whereHas(
'employee.user',
fn ($q) => $q
->where('is_active', true)
->whereHas(
'roles',
fn ($r) => $r
->whereHas(
'permissions',
fn ($p) => $p
->where('name', 'attendances.create')
)
)
)->count();
$absent = max(0, $totalEmployees - $present - $onLeave);
return [
'total_employees' => $totalEmployees,
'present' => $present,
'absent' => $absent,
'on_leave' => $onLeave,
'percentage' => $totalEmployees > 0 ? round(($present / $totalEmployees) * 100) : 0,
];
}
public function getRevenueSummary(?User $user = null): array
{
$today = Carbon::now()->toDateString();
$baseQuery = Order::where('status', OrderStatus::COMPLETED)
->whereDate('created_at', $today);
if ($user && $this->isMarketingUser($user)) {
$baseQuery->where('marketing_id', $user->id);
}
if ($user && $this->isCashierUser($user)) {
$baseQuery->whereIn('created_by_id', $this->getCashierUserIds());
}
$stats = (clone $baseQuery)
->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(nego_price), 0) as total_deduction')
->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs')
->first();
$payrollTotal = (int) Payroll::where('status', PayrollStatus::PAID)
->whereDate('paid_at', $today)
->sum('total_amount');
$expenseTotal = (int) Expense::whereDate('created_at', $today)
->sum('amount');
$totalRevenue = (int) $stats->total_revenue;
$totalCogs = (int) $stats->total_cogs;
$gross = $totalRevenue - $totalCogs;
$net = $gross - $payrollTotal - $expenseTotal;
$orderIds = (clone $baseQuery)->pluck('id');
$totalProductsSold = (int) OrderItem::whereIn('order_id', $orderIds)->sum('quantity');
return [
'total_revenue' => $totalRevenue,
'total_discount' => (int) $stats->total_discount,
'total_cogs' => $totalCogs,
'total_deduction' => (int) $stats->total_deduction,
'gross' => $gross,
'net' => $net,
'payroll_total' => $payrollTotal,
'expense_total' => $expenseTotal,
'total_orders' => (int) $stats->total_orders,
'total_products_sold' => $totalProductsSold,
];
}
public function getExpenseSummary(?User $user = null): array
{
$today = Carbon::now()->toDateString();
if ($user && $this->isMarketingUser($user)) {
$employee = $user->employee;
$expenseTotal = Expense::whereDate('created_at', $today)
->where('created_by_id', $user->id)
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
$advanceQuery = EmployeeAdvance::whereDate('created_at', $today)
->disbursed();
if ($employee) {
$advanceQuery->where('employee_id', $employee->id);
} else {
$advanceQuery->whereRaw('0 = 1');
}
$advanceTotal = $advanceQuery
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
return [
'total' => (int) $expenseTotal->total + (int) $advanceTotal->total,
'expense_total' => (int) $expenseTotal->total,
'advance_total' => (int) $advanceTotal->total,
];
}
$expenseQuery = Expense::whereDate('created_at', $today);
if ($user && $this->isCashierUser($user)) {
$expenseQuery->whereIn('created_by_id', $this->getCashierUserIds());
}
$expenseTotal = $expenseQuery
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
$advanceQuery = EmployeeAdvance::whereDate('created_at', $today)
->disbursed();
if ($user && $this->isCashierUser($user)) {
$advanceQuery->whereHas('employee.user', fn ($q) => $q->whereIn('id', $this->getCashierUserIds()));
}
$advanceTotal = $advanceQuery
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
return [
'total' => (int) $expenseTotal->total + (int) $advanceTotal->total,
'expense_total' => (int) $expenseTotal->total,
'advance_total' => (int) $advanceTotal->total,
];
}
public function getOrderStats(?User $user = null): array
{
$today = Carbon::now()->toDateString();
$baseQuery = Order::whereDate('created_at', $today);
if ($user && $this->isMarketingUser($user)) {
$baseQuery->where('marketing_id', $user->id);
}
if ($user && $this->isCashierUser($user)) {
$baseQuery->whereIn('created_by_id', $this->getCashierUserIds());
}
$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 = (clone $baseQuery)
->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();
}
private function isMarketingUser(User $user): bool
{
return $user->hasAnyRole([
Role::MARKETING_OFFLINE->value,
Role::MARKETING_ONLINE->value,
]);
}
private function isCashierUser(User $user): bool
{
return $user->hasRole(Role::CASHIER->value);
}
private function getCashierUserIds(): Collection
{
return User::whereHas('roles', fn ($q) => $q->where('name', Role::CASHIER->value))->pluck('id');
}
private function applyMarketingFilter(Builder $query, User $user, string $column = 'marketing_id'): Builder
{
if ($this->isMarketingUser($user)) {
$query->where($column, $user->id);
}
return $query;
}
}