ultimatepos/Modules/Maintenance/Services/MaintenanceDashboardService.php

265 lines
9.3 KiB
PHP

<?php
namespace Modules\Maintenance\Services;
use App\Business;
use Modules\Maintenance\Models\Equipment;
use Modules\Maintenance\Models\FailureReport;
use Modules\Maintenance\Models\Overhaul;
use Modules\Maintenance\Models\PreventiveSchedule;
use Modules\Maintenance\Models\RebuildProject;
use Modules\Maintenance\Models\SparePart;
use Modules\Maintenance\Models\WorkOrder;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\Maintenance\Utils\MaintenanceUtil;
class MaintenanceDashboardService
{
public function __construct(
protected MaintenanceUtil $maintenanceUtil
) {}
public function getStats(Business $business): array
{
$bid = $business->id;
$today = Carbon::today();
$monthStart = Carbon::now()->startOfMonth();
$totalEquipment = Equipment::where('business_id', $bid)->count();
$activeEquipment = Equipment::where('business_id', $bid)->where('status', 'active')->count();
$underOverhaul = Equipment::where('business_id', $bid)->where('status', 'under_overhaul')->count();
$upcomingOverhauls = Overhaul::where('business_id', $bid)
->whereIn('status', ['planned', 'approved', 'waiting'])
->where('start_date', '>=', $today)
->where('start_date', '<=', $today->copy()->addDays(30))
->count();
$delayedOverhauls = Overhaul::where('business_id', $bid)
->whereIn('status', ['planned', 'approved', 'waiting', 'in_progress'])
->where('end_date', '<', $today)
->count();
$todayTasks = WorkOrder::where('business_id', $bid)
->whereIn('status', ['open', 'in_progress'])
->whereDate('scheduled_at', $today)
->count();
$monthlyCosts = (int) WorkOrder::where('business_id', $bid)
->where('status', 'completed')
->where('completed_at', '>=', $monthStart)
->sum('cost')
+ (int) Overhaul::where('business_id', $bid)
->where('status', 'completed')
->where('updated_at', '>=', $monthStart)
->sum('actual_cost');
$avgHealth = (float) Equipment::where('business_id', $bid)->avg('health_percentage');
$mttr = $this->calculateMttr($bid);
$mtbf = $this->calculateMtbf($bid);
$maintenanceStats = [
'preventive_due' => PreventiveSchedule::where('business_id', $bid)
->where('is_active', true)
->where('next_due_at', '<=', now()->addDays(7))
->count(),
'open_work_orders' => WorkOrder::where('business_id', $bid)
->whereIn('status', ['open', 'in_progress'])
->count(),
'completed_this_month' => WorkOrder::where('business_id', $bid)
->where('status', 'completed')
->where('completed_at', '>=', $monthStart)
->count(),
'low_stock_parts' => SparePart::where('business_id', $bid)
->whereColumn('quantity', '<=', 'min_stock')
->count(),
];
return [
'equipment' => [
'total' => $totalEquipment,
'active' => $activeEquipment,
'under_overhaul' => $underOverhaul,
'health_percentage' => round($avgHealth, 1),
],
'overhauls' => [
'upcoming' => $upcomingOverhauls,
'delayed' => $delayedOverhauls,
],
'tasks' => [
'today' => $todayTasks,
],
'costs' => [
'monthly' => $monthlyCosts,
],
'kpi' => [
'mttr_hours' => $mttr,
'mtbf_hours' => $mtbf,
],
'maintenance' => $maintenanceStats,
];
}
public function getRecentActivities(Business $business, int $limit = 10): array
{
if (! Schema::hasTable('maintenance_activity_logs')) {
return [];
}
return DB::table('maintenance_activity_logs')
->where('business_id', $business->id)
->orderByDesc('created_at')
->limit($limit)
->get()
->toArray();
}
public function getCalendarEvents(Business $business, string $start, string $end): array
{
$bid = $business->id;
$events = [];
$overhauls = Overhaul::where('business_id', $bid)
->whereBetween('start_date', [$start, $end])
->with('equipment:id,name,code')
->get();
foreach ($overhauls as $o) {
$events[] = [
'id' => 'overhaul-'.$o->id,
'type' => 'overhaul',
'title' => $o->title,
'start' => $o->start_date?->toDateString(),
'end' => $o->end_date?->toDateString(),
'start_jalali' => $this->maintenanceUtil->formatDate($o->start_date),
'end_jalali' => $this->maintenanceUtil->formatDate($o->end_date),
'status' => $o->status,
'equipment' => $o->equipment?->name,
];
}
$schedules = PreventiveSchedule::where('business_id', $bid)
->where('is_active', true)
->whereBetween('next_due_at', [$start, $end])
->with('equipment:id,name,code')
->get();
foreach ($schedules as $s) {
$events[] = [
'id' => 'pm-'.$s->id,
'type' => 'preventive',
'title' => $s->title,
'start' => $s->next_due_at?->toDateString(),
'start_jalali' => $this->maintenanceUtil->formatDateTime($s->next_due_at),
'equipment' => $s->equipment?->name,
];
}
$workOrders = WorkOrder::where('business_id', $bid)
->whereBetween('scheduled_at', [$start, $end])
->with('equipment:id,name,code')
->get();
foreach ($workOrders as $w) {
$events[] = [
'id' => 'wo-'.$w->id,
'type' => 'work_order',
'title' => $w->work_order_number,
'start' => $w->scheduled_at?->toDateString(),
'start_jalali' => $this->maintenanceUtil->formatDateTime($w->scheduled_at),
'status' => $w->status,
'equipment' => $w->equipment?->name,
];
}
$rebuilds = RebuildProject::where('business_id', $bid)
->whereNotIn('status', ['draft', 'cancelled'])
->where(function ($q) use ($start, $end) {
$q->whereBetween('planned_start_date', [$start, $end])
->orWhereBetween('planned_end_date', [$start, $end]);
})
->get();
foreach ($rebuilds as $r) {
$events[] = [
'id' => 'rebuild-'.$r->id,
'type' => 'rebuild',
'title' => $r->title,
'start' => $r->planned_start_date?->toDateString(),
'end' => $r->planned_end_date?->toDateString(),
'start_jalali' => $this->maintenanceUtil->formatDate($r->planned_start_date),
'end_jalali' => $this->maintenanceUtil->formatDate($r->planned_end_date),
'status' => $r->status,
];
}
return $events;
}
public function getChartData(Business $business): array
{
$bid = $business->id;
$months = collect(range(5, 0))->map(fn ($i) => Carbon::now()->subMonths($i));
$costByMonth = $months->map(function ($month) use ($bid) {
$start = $month->copy()->startOfMonth();
$end = $month->copy()->endOfMonth();
return [
'label' => $this->maintenanceUtil->formatMonthLabel($month),
'cost' => (int) WorkOrder::where('business_id', $bid)
->where('status', 'completed')
->whereBetween('completed_at', [$start, $end])
->sum('cost'),
];
});
$statusDistribution = Equipment::where('business_id', $bid)
->select('status', DB::raw('count(*) as count'))
->groupBy('status')
->pluck('count', 'status')
->toArray();
return [
'monthly_costs' => $costByMonth->values()->toArray(),
'equipment_by_status' => $statusDistribution,
];
}
protected function calculateMttr(int $businessId): float
{
if (! Schema::hasTable('maintenance_failure_reports')) {
return 0;
}
$avg = FailureReport::where('business_id', $businessId)
->where('repair_hours', '>', 0)
->avg('repair_hours');
return round((float) $avg, 2);
}
protected function calculateMtbf(int $businessId): float
{
if (! Schema::hasTable('maintenance_failure_reports')) {
return 0;
}
$equipmentIds = Equipment::where('business_id', $businessId)->pluck('id');
if ($equipmentIds->isEmpty()) {
return 0;
}
$failureCount = FailureReport::where('business_id', $businessId)->count();
if ($failureCount === 0) {
return 0;
}
$totalHours = Equipment::where('business_id', $businessId)->sum('working_hours');
return round($totalHours / $failureCount, 2);
}
}