id(); $wos = WorkOrder::with(['client:id,name', 'vehicle:id,plate,make,model', 'works']) ->where('master_id', $userId) ->whereNotIn('status', ['done', 'cancelled']) ->orderBy('opened_at') ->get() ->map(fn ($wo) => [ 'id' => $wo->id, 'number' => $wo->number, 'status' => $wo->status, 'client_name' => $wo->client?->name, 'vehicle' => trim(($wo->vehicle?->make ?? '') . ' ' . ($wo->vehicle?->model ?? '')), 'plate' => $wo->vehicle?->plate, 'complaint' => $wo->complaint, 'eta_at' => $wo->eta_at?->toIso8601String(), 'works' => $wo->works->map(fn ($w) => $this->workPayload($w))->all(), ]); return response()->json(['data' => $wos]); } /** POST /api/v1/mechanic/tasks/{work}/start */ public function startTask(WorkOrderWork $work): JsonResponse { $this->authorizeOwn($work); $work->start(); return response()->json(['data' => $this->workPayload($work->fresh())]); } public function pauseTask(WorkOrderWork $work): JsonResponse { $this->authorizeOwn($work); $work->pause(); return response()->json(['data' => $this->workPayload($work->fresh())]); } public function resumeTask(WorkOrderWork $work): JsonResponse { $this->authorizeOwn($work); $work->resume(); return response()->json(['data' => $this->workPayload($work->fresh())]); } public function doneTask(WorkOrderWork $work): JsonResponse { $this->authorizeOwn($work); $work->markDone(); return response()->json(['data' => $this->workPayload($work->fresh())]); } public function blockTask(Request $request, WorkOrderWork $work): JsonResponse { $this->authorizeOwn($work); $data = $request->validate([ 'reason' => 'required|in:' . implode(',', array_keys(WorkOrderWork::BLOCK_REASONS)), 'note' => 'nullable|string|max:1000', ]); $work->block($data['reason'], $data['note'] ?? null); return response()->json(['data' => $this->workPayload($work->fresh())]); } /** GET /api/v1/mechanic/kpi?period=2026-06 — own efficiency aggregates. */ public function kpi(Request $request): JsonResponse { $userId = auth()->id(); $period = $request->query('period', now()->format('Y-m')); [$y, $m] = explode('-', $period); $rows = WorkOrderWork::whereHas('workOrder', fn ($q) => $q->where('master_id', $userId)) ->where('mechanic_status', 'done') ->whereYear('mechanic_done_at', $y) ->whereMonth('mechanic_done_at', $m) ->get(); $totalNorm = (float) $rows->sum('hours'); $totalActual = (float) $rows->sum('actual_hours'); $tasksDone = $rows->count(); $totalRevenue = (float) $rows->sum('total'); $efficiencyPct = $totalNorm > 0 ? round(100 * $totalActual / $totalNorm) : null; return response()->json([ 'period' => $period, 'tasks_done' => $tasksDone, 'norm_hours' => round($totalNorm, 2), 'actual_hours' => round($totalActual, 2), 'efficiency_pct' => $efficiencyPct, 'revenue_manopere' => round($totalRevenue, 2), ]); } private function workPayload(WorkOrderWork $w): array { return [ 'id' => $w->id, 'name' => $w->name, 'mechanic_status' => $w->mechanic_status, 'norm_hours' => (float) $w->hours, 'actual_hours' => (float) $w->actual_hours, 'efficiency_pct' => $w->efficiencyPct(), 'efficiency_class' => $w->efficiencyClass(), 'block_reason' => $w->block_reason, 'block_note' => $w->block_note, 'mechanic_started_at' => $w->mechanic_started_at?->toIso8601String(), 'mechanic_done_at' => $w->mechanic_done_at?->toIso8601String(), ]; } private function authorizeOwn(WorkOrderWork $work): void { if ($work->workOrder?->master_id !== auth()->id()) { abort(403, 'Work belongs to a different mechanic.'); } } }