store/app/Services/System/DashboardService.php
Yoga Pangestu b3184de155
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (8.3) (push) Waiting to run
tests / ci (8.4) (push) Waiting to run
tests / ci (8.5) (push) Waiting to run
feat: enhance attendance retrieval by filtering roles based on view permissions
2026-07-16 15:10:06 +07:00

298 lines
12 KiB
PHP

<?php
namespace App\Services\System;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentType;
use App\Enums\Permission;
use App\Enums\PriceType;
use App\Enums\Role;
use App\Models\Attendance;
use App\Models\CashAccount;
use App\Models\CashTransaction;
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\Purchase;
use App\Models\User;
use Carbon\Carbon;
class DashboardService
{
public function getTodayAttendanceForUser(User $user): ?array
{
$employee = $user->employee;
if ($employee === null) {
return null;
}
$attendance = Attendance::query()
->with('media')
->where('employee_id', $employee->id)
->whereDate('attendance_date', today())
->first();
return $attendance?->toArray();
}
public function isOnLeaveTodayForUser(User $user): bool
{
$employee = $user->employee;
if ($employee === null) {
return false;
}
return LeaveRequest::query()
->approved()
->where('employee_id', $employee->id)
->whereDate('start_date', '<=', today())
->whereDate('end_date', '>=', today())
->exists();
}
public function canCheckInForUser(User $user): bool
{
return $user->can('attendances.create') && $user->employee !== null;
}
public function getAttendance(): array
{
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
$attendanceRoles = collect(Role::cases())
->filter(fn (Role $role) => in_array(Permission::ATTENDANCES_VIEW, $role->permissions()))
->map(fn (Role $role) => $role->value)
->values()
->toArray();
$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);
}
$employeeQuery->whereHas('user.roles', fn ($q) => $q->whereIn('name', $attendanceRoles));
$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 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 getRevenueSummary(): 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))
->whereDate('created_at', Carbon::today())
->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')
->whereDate('created_at', Carbon::today())
->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)
->whereDate('orders.created_at', Carbon::today())
->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))
->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 + (int) ($totalCostPrice ?? 0),
'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
];
}
public function getExpenseSummary(): array
{
$today = Carbon::today();
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
$purchase = Purchase::query()
->whereDate('created_at', $today)
->when(! $isSuper, fn ($q) => $q->where('created_by_id', $user->id))
->selectRaw('COALESCE(SUM(total), 0) as total')
->first();
$expenses = Expense::query()
->whereDate('created_at', $today)
->when(! $isSuper, fn ($q) => $q->where('created_by_id', $user->id))
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
$employeeAdvance = EmployeeAdvance::query()
->whereDate('created_at', $today)
->when(! $isSuper, fn ($q) => $q->where('employee_id', $user->employee?->id))
->selectRaw('COALESCE(SUM(amount), 0) as total')
->first();
$purchaseTotal = (int) ($purchase->total ?? 0);
if ($user?->hasRole(Role::ADMIN_TOKO->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 getOrderStats(): array
{
/** @var User|null $user */
$user = auth()->user();
$isSuper = $user?->hasAnyRole(['developer', 'owner', 'direktur']) ?? false;
$isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
$byChannel = 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))
->whereDate('created_at', Carbon::today())
->selectRaw('channel, COUNT(*) as count, SUM(total_amount) as total')
->groupBy('channel')
->get()
->map(fn ($item) => [
'channel' => $item->channel instanceof OrderChannel ? $item->channel->value : $item->channel,
'label' => $item->channel instanceof OrderChannel ? $item->channel->label() : OrderChannel::from($item->channel)->label(),
'count' => (int) $item->count,
'total' => (int) $item->total,
]);
$byPaymentType = 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))
->whereDate('created_at', Carbon::today())
->selectRaw('payment_type, COUNT(*) as count, SUM(total_amount) as total')
->groupBy('payment_type')
->get()
->map(fn ($item) => [
'payment_type' => $item->payment_type instanceof PaymentType ? $item->payment_type->value : $item->payment_type,
'label' => $item->payment_type instanceof PaymentType ? $item->payment_type->label() : PaymentType::from($item->payment_type)->label(),
'count' => (int) $item->count,
'total' => (int) $item->total,
]);
$byMarketing = 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))
->whereNotNull('marketing_id')
->whereDate('orders.created_at', Carbon::today())
->join('users', 'orders.marketing_id', '=', 'users.id')
->leftJoin('user_profiles', 'users.id', '=', 'user_profiles.user_id')
->selectRaw('COALESCE(user_profiles.full_name, users.username) as name, COUNT(*) as count, SUM(orders.total_amount) as total')
->groupBy('orders.marketing_id', 'users.username', 'user_profiles.full_name')
->orderByDesc('total')
->limit(5)
->get()
->map(fn ($item) => [
'name' => $item->name,
'count' => (int) $item->count,
'total' => (int) $item->total,
]);
$byStatus = 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))
->whereDate('created_at', Carbon::today())
->selectRaw('status, COUNT(*) as count')
->groupBy('status')
->get()
->map(fn ($item) => [
'status' => $item->status instanceof OrderStatus ? $item->status->value : $item->status,
'label' => $item->status instanceof OrderStatus ? $item->status->label() : OrderStatus::from($item->status)->label(),
'count' => (int) $item->count,
]);
return [
'by_channel' => $byChannel->toArray(),
'by_payment_type' => $byPaymentType->toArray(),
'by_marketing' => $byMarketing->toArray(),
'by_status' => $byStatus->toArray(),
];
}
}