'Cairan Steril', 'buku02' => 'CCI', 'buku03' => 'Darah', 'buku04' => 'HIV', 'buku05' => 'Kuku, Kulit, Adnexa', 'buku06' => 'Kultur TB', 'buku07' => 'Pus dan Jaringan', 'buku08' => 'Sputum', 'buku09' => 'Swab', 'buku10' => 'TCM TB', 'buku11' => 'TCM non-TB', 'buku12' => 'Urine', 'buku13' => 'Feces', 'buku0' => 'Lainnya', ]; protected function bookPoliMap(): array { return [ 'buku01' => [15,16,43,47,48,66,96,205,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223], 'buku02' => [20], 'buku03' => [7,8,13,17,29,33,44,107,114,115,199,200,201,202], 'buku04' => [30], 'buku05' => [165,179,180,181,183,184,185,187,188,189,190,192,194,195,196,198], 'buku06' => [26,40,45,52,53,56,60,61,68,69,75,92,102,122,123,136,137,144,145,152,153,233], 'buku07' => [18,19,32,35,46,49,50,54,57,58,59,72,160,182,186,191,193,197,206,207,208,224,225,226,227,228,234,235,236,237], 'buku08' => [1,12,23,24,36,39,229,230,231,232], 'buku09' => [2,4,5,9,10,11,21,22,27,34,41,42,51,63,64,65,85,86,87,88,93,97,118,67,121,125,126,127,128,129,130,132,133,134,135,140,147,158,159,161,162,163,164,166,167,168,169,170,171,172,173,174,175,176,177,178], 'buku10' => [14,28,55,62,94,104,105,124,138,146], 'buku11' => [79,80,81,82,83,84,203], 'buku12' => [3,6,25,31,154,155,156,157], 'buku13' => [37,38,148,149,204], ]; } protected function activeExaminationQuery() { return Periksa::query() ->where(function ($query) { $query->where(function ($sub) { $sub->whereNotIn('status', ['Selesai', 'Arsip']) ->where('status', 'not like', 'Dibatalkan%'); })->orWhereNull('status'); }); } protected function userFromRequest(Request $request): ?User { $header = (string) $request->bearerToken(); if ($header === '') { return null; } try { $payload = Crypt::decryptString($header); $data = json_decode($payload, true); $user = User::find($data['id'] ?? null); if (!$user || ($data['password'] ?? '') !== $user->password) { return null; } return $user; } catch (\Throwable $e) { return null; } } protected function requireUser(Request $request): User { $user = $this->userFromRequest($request); abort_if(!$user, 401, 'Token login tidak valid.'); return $user; } protected function tokenFor(User $user): string { return Crypt::encryptString(json_encode([ 'id' => $user->id, 'password' => $user->password, 'issued_at' => now()->toDateTimeString(), ])); } protected function resolveTargetDays($modaliti2): int { return (!empty($modaliti2) && is_numeric($modaliti2) && intval($modaliti2) > 0) ? intval($modaliti2) : 1; } protected function resolveMasterFromPoliId($poliId): string { foreach ($this->bookPoliMap() as $master => $poliIds) { if (in_array((int) $poliId, $poliIds, true)) { return $master; } } return 'buku0'; } protected function examinationPayload(Periksa $row): array { $formatDate = function ($value): ?string { if (!$value) { return null; } try { return Carbon::parse($value)->format('Y-m-d H:i:s'); } catch (\Throwable $e) { return (string) $value; } }; return [ 'id' => $row->id, 'master' => $this->resolveMasterFromPoliId($row->poli_id), 'master_label' => $this->bookLabels[$this->resolveMasterFromPoliId($row->poli_id)] ?? 'Lainnya', 'nofoto' => $row->nofoto, 'noregister' => $row->noregister, 'nmpasien' => $row->nmpasien, 'jkpasien' => $row->jkpasien, 'usia' => $row->usia, 'tgllahirpasien' => $row->tgllahirpasien, 'tlppasien' => $row->tlppasien, 'alamatpasien' => $row->alamatpasien, 'reques' => $row->reques, 'asalpasien' => $row->asalpasien, 'ruangan' => $row->ruangan, 'klinisi' => $row->klinisi, 'nmdokter' => $row->nmdokter, 'nm_spesimen' => $row->nm_spesimen, 'tanggalsampel' => $row->tanggalsampel, 'pengambilan' => $row->pengambilan, 'asalpengirim' => $row->asalpengirim, 'status' => $row->status ?: 'NEW', 'daftar' => $formatDate($row->daftar), 'verifikasi' => $formatDate($row->verifikasi), ]; } public function login(Request $request) { $request->validate([ 'username' => ['required'], 'password' => ['required'], ]); $user = User::where('username', $request->input('username'))->first(); if (!$user || !Hash::check((string) $request->input('password'), $user->password)) { return response()->json(['message' => 'Username atau password salah.'], 401); } $photo = $user->getPhoto->xfile ?? '/doctor.png'; $user->active_status = 1; $user->save(); return response()->json([ 'token' => $this->tokenFor($user), 'user' => [ 'id' => $user->id, 'nama' => $user->nama, 'username' => $user->username, 'previlage' => $user->previlage, 'photo' => url($photo), ], ]); } public function ping() { return response()->json([ 'status' => 'ok', 'application' => config('global.namaapps', config('app.name')), 'time' => now()->toDateTimeString(), ]); } public function dashboard(Request $request) { $user = $this->requireUser($request); $allData = $this->activeExaminationQuery()->get(); $mapped = collect($this->bookPoliMap())->flatten()->all(); $books = []; foreach ($this->bookLabels as $master => $label) { $count = $master === 'buku0' ? $allData->filter(fn ($item) => !in_array($item->poli_id, $mapped))->count() : $allData->whereIn('poli_id', $this->bookPoliMap()[$master] ?? [])->count(); $books[] = ['master' => $master, 'label' => $label, 'total' => $count]; } return response()->json([ 'user' => [ 'id' => $user->id, 'nama' => $user->nama, 'username' => $user->username, 'previlage' => $user->previlage, ], 'summary' => [ 'total_ppds' => User::where('previlage', 'ppds')->count(), 'butuh_verifikasi' => Periksa::where('status', 'like', '%Un Verified%')->count(), 'antrian_hari_ini' => Periksa::whereDate('daftar', Carbon::today())->count(), 'antrian_sinkron' => PeriksaSYNC::where('created_by', $user->username)->where('status', '')->count(), ], 'books' => $books, 'early_warning_groups' => $this->earlyWarningGroups(), ]); } public function examinations(Request $request, string $master) { $this->requireUser($request); $query = $this->activeExaminationQuery(); $mapped = collect($this->bookPoliMap())->flatten()->all(); if ($master === 'buku0') { $query->whereNotIn('poli_id', $mapped); } elseif (isset($this->bookPoliMap()[$master])) { $query->whereIn('poli_id', $this->bookPoliMap()[$master]); } else { return response()->json(['message' => 'Kategori pemeriksaan tidak dikenal.'], 404); } $search = trim((string) $request->query('search', '')); if ($search !== '') { $query->where(function ($q) use ($search) { $q->where('nofoto', 'like', "%{$search}%") ->orWhere('noregister', 'like', "%{$search}%") ->orWhere('nmpasien', 'like', "%{$search}%") ->orWhere('reques', 'like', "%{$search}%"); }); } $items = $query->orderBy('daftar', 'ASC')->limit(250)->get()->map(fn ($row) => $this->examinationPayload($row)); return response()->json([ 'master' => $master, 'label' => $this->bookLabels[$master], 'items' => $items, ]); } public function examination(Request $request, int $id) { $this->requireUser($request); $row = Periksa::find($id); abort_if(!$row, 404, 'Data pemeriksaan tidak ditemukan.'); return response()->json([ 'item' => $this->examinationPayload($row), 'expertise_url' => url('mobile/expertise/'.$row->id).'?token='.rawurlencode((string) $request->bearerToken()), 'result_url' => $row->nofoto ? url('hasil/'.$row->nofoto) : null, ]); } public function earlyWarning(Request $request) { $this->requireUser($request); return response()->json(['groups' => $this->earlyWarningGroups()]); } protected function earlyWarningGroups(): array { $now = Carbon::now(); $rows = Periksa::leftJoin('poli', 'periksa.poli_id', '=', 'poli.id') ->select('periksa.id', 'periksa.poli_id', 'periksa.nofoto', 'periksa.noregister', 'periksa.nmpasien', 'periksa.reques', 'periksa.asalpasien', 'periksa.daftar', 'periksa.status', 'poli.subpoli', 'poli.modaliti2') ->whereNotNull('periksa.status') ->where('periksa.status', '<>', '') ->whereRaw('LOWER(periksa.status) <> ?', ['selesai']) ->whereRaw('LOWER(periksa.status) not like ?', ['%dibatalkan%']) ->whereNotNull('periksa.daftar') ->orderBy('periksa.daftar', 'ASC') ->get(); $warnings = []; foreach ($rows as $row) { $targetDays = $this->resolveTargetDays($row->modaliti2); $registeredAt = Carbon::parse($row->daftar); $deadline = $registeredAt->copy()->addDays($targetDays)->endOfDay(); $remainingMinutes = $now->diffInMinutes($deadline, false); $warningLimit = $targetDays > 1 ? 1440 : 60; $isOverdue = $now->gt($deadline); if ($isOverdue || $remainingMinutes <= $warningLimit) { $warnings[] = array_merge($this->examinationPayload($row), [ 'subpoli' => $row->subpoli ?? 'Tanpa Subpoli', 'target_hari' => $targetDays, 'target_selesai' => $deadline->format('Y-m-d H:i:s'), 'sisa_menit' => $remainingMinutes, 'warning_label' => $isOverdue ? 'Melewati Target' : ($targetDays > 1 ? 'Kurang dari 1 Hari' : 'Kurang dari 1 Jam'), ]); } } return collect($warnings) ->groupBy(fn ($item) => $item['subpoli'] ?: 'Tanpa Subpoli') ->map(fn ($items, $subpoli) => [ 'subpoli' => $subpoli, 'total' => count($items), 'items' => array_values($items->toArray()), ]) ->values() ->toArray(); } public function openExpertise(Request $request, int $periksa) { $token = (string) $request->query('token', ''); $request->headers->set('Authorization', 'Bearer '.$token); $user = $this->userFromRequest($request); abort_if(!$user, 401, 'Token login tidak valid.'); Auth::loginUsingId($user->id); $photo = $user->getPhoto->xfile ?? '/doctor.png'; Session::put('id', $user->id); Session::put('nama', $user->nama); Session::put('username', $user->username); Session::put('previlage', $user->previlage); Session::put('photo', $photo); Session::save(); return redirect()->route('mikro.expertise', ['periksa' => $periksa, 'return_to' => 'dashboard']); } }