validate([ 'username' => 'required|string', 'password' => 'required|string', 'kodeprovider' => 'required|string', ]); $organization = Organization::query()->where('code', $request->kodeprovider)->first(); if (!$organization) { return $this->headerError('Kode provider tidak ditemukan'); } $provider = Provider::query()->firstOrNew([ 'organization_id' => $organization->id, 'username' => $request->username, ]); if ($provider->exists) { $isPasswordValid = Hash::check($request->password, $provider->password) || $provider->password === $request->password; if (!$isPasswordValid) { return $this->headerError('Username atau password tidak valid'); } } else { $provider->password = Hash::make($request->password); } $provider->code = $request->kodeprovider; $provider->status = 'active'; $provider->header_token = Str::random(64); $provider->token = Str::random(64); $provider->save(); return response()->json([ 'header-token' => $provider->header_token, 'userid' => $provider->id, 'usertoken' => $provider->token, 'kodeprovider' => $organization->code, 'namaprovider' => $organization->name, 'errornumber' => 0, 'messagestring' => 'Success', ]); } public function checkEligibilitasPeserta(Request $request) { $request->validate([ 'nokartu' => 'required|string', 'kodeprovider' => 'required|string', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $member = Member::query() ->with(['person', 'currentCorporate', 'currentPolicy', 'memberPlans.plan']) ->where('member_id', $request->nokartu) ->first(); if (!$member) { return $this->statusError('Peserta tidak ditemukan'); } $benefits = $member->memberPlans ->map(function (MemberPlan $memberPlan) { $plan = $memberPlan->plan; if (!$plan || empty($plan->code)) { return null; } return [ 'kodebenefit' => $plan->code, 'namabenefit' => $plan->corporate_plan_id, 'planid' => $plan->code, ]; }) ->filter() ->unique('kodebenefit') ->values(); return response()->json([ 'Status' => $this->okStatus(), 'Data' => [ 'nokartu' => $member->member_id, 'memberid' => (string) $member->id, 'namapeserta' => $member->full_name, 'nomorbpjs' => $member->bpjs_id, 'jeniskelamin' => $member->gender_code, 'tanggallahir' => $this->isoDate($member->birth_date), 'hubungankeluarga' => $member->relation_with_principal, 'namaperusahaan' => optional($member->currentCorporate)->name, 'pesertavip' => 'N', 'namapenjamin' => optional($organization)->name, 'nomorpolis' => optional($member->currentPolicy)->code, 'tglmulaipolis' => $this->isoDate(optional($member->currentPolicy)->start), 'tglberakhirpolis' => $this->isoDate(optional($member->currentPolicy)->end), 'phone' => optional($member->person)->phone, 'email' => $member->email, ], 'Benefit' => $benefits, ]); } public function createPendaftaran(Request $request) { $request->validate([ 'kodeprovider' => 'required|string', 'kodebenefit' => 'required|string', 'statusrujukan' => 'nullable|string', 'nomorrujukan' => 'nullable|string', 'keterangan' => 'nullable|string', 'nomorsep' => 'nullable|string', 'nokartu' => 'required|string', 'kelaskamar' => 'nullable|string', 'cobbpjs' => 'nullable|numeric', 'notransaksiprovider' => 'nullable|string', 'inacbgscode' => 'nullable|string', 'inacbgsamount' => 'nullable|numeric', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $member = Member::query() ->with(['person', 'currentCorporate', 'currentPolicy']) ->where('member_id', $request->nokartu) ->first(); if (!$member) { return $this->statusError('Peserta tidak ditemukan'); } $plan = $this->resolvePlan($member, $request->kodebenefit); $generatedLogCode = $this->generateNextRequestLogCode($organization, $member); $requestLog = RequestLog::query()->create([ 'code' => $generatedLogCode, 'organization_id' => $organization->id, 'member_id' => $member->id, 'plan_id' => optional($plan)->id, 'policy_id' => optional($member->currentPolicy)->id ?? 0, 'payment_type' => 'cashless', 'service_code' => $request->kodebenefit, 'type_of_member' => 'member', 'status' => 'approved', 'source' => 'api', 'keterangan' => $request->keterangan, 'hak_kamar_pasien' => $request->kelaskamar ?? '', 'penempatan_kamar' => $request->kelaskamar, 'total_cob' => $request->cobbpjs, 'nominal' => 0, 'import_system' => 0, 'nomor_sep' => $request->nomorsep, 'inacbgs_code' => $request->inacbgscode, 'inacbgs_amount' => $request->inacbgsamount, 'no_transaksi_provider' => $request->notransaksiprovider, 'diagnosis' => '', 'reason' => '', 'reason_final' => '', 'catatan' => '', 'nomor_rujukan' => $request->nomorrujukan, 'status_rujukan' => $request->statusrujukan, 'submission_date' => now(), 'admission_date' => now(), ]); $limitSubBenefit = collect(); if ($plan) { $limitSubBenefit = CorporateBenefit::query() ->with('benefit') ->where('plan_id', $plan->id) ->get() ->map(function (CorporateBenefit $corporateBenefit) { return [ 'kodesubbenefit' => optional($corporateBenefit->benefit)->code, 'namasubbenefit' => optional($corporateBenefit->benefit)->description, 'batasan' => (string) ($corporateBenefit->limit_amount ?? 0), ]; }) ->filter(function (array $item) { return !empty($item['kodesubbenefit']); }); } $this->sendProviderOnlineEmail('pendaftaran baru', $requestLog, $organization); return response()->json([ 'Status' => $this->okStatus(), 'Data' => [$this->buildClaimHeader($requestLog, $organization)], 'LimitSubBenefit' => $limitSubBenefit->values(), ]); } public function createPengesahan(Request $request) { $request->validate([ 'noklaim' => 'required|string', 'kodeprovider' => 'required|string', 'tanggalkeluar' => 'nullable|date', 'kodediagnosa' => 'nullable|string', 'inacbgscode' => 'nullable|string', 'inacbgsamount' => 'nullable|numeric', 'daftarbiaya' => 'nullable|array', 'daftarbiaya.*.kodesubbenefit' => 'required_with:daftarbiaya|string', 'daftarbiaya.*.biayaaju' => 'required_with:daftarbiaya|numeric', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $requestLog = RequestLog::query() ->with(['member.person', 'member.currentCorporate', 'member.currentPolicy', 'plan']) ->where('code', $request->noklaim) ->where('organization_id', $organization->id) ->first(); if (!$requestLog) { return $this->statusError('Nomor klaim tidak ditemukan'); } $benefitsByCode = $this->resolveBenefitCodes($request->daftarbiaya ?? []); if (isset($benefitsByCode['error'])) { return $this->statusError($benefitsByCode['error']); } $requestLog->update([ 'status_final_log' => 'approve', 'final_log' => true, 'discharge_date' => $request->tanggalkeluar, 'diagnosis' => $request->kodediagnosa, 'inacbgs_code' => $request->inacbgscode ?? $requestLog->inacbgs_code, 'inacbgs_amount' => $request->inacbgsamount ?? $requestLog->inacbgs_amount, ]); foreach (($request->daftarbiaya ?? []) as $biaya) { $benefit = $benefitsByCode[$biaya['kodesubbenefit']]; RequestLogBenefit::query()->updateOrCreate( [ 'request_log_id' => $requestLog->id, 'benefit_id' => $benefit->id, ], [ 'amount_incurred' => $biaya['biayaaju'], 'amount_approved' => 0, 'amount_not_approved' => 0, 'excess_paid' => 0, 'keterangan' => null, ] ); } $requestLog->load(['requestLogBenefits.benefit']); $biayaResponse = $requestLog->requestLogBenefits->map(function (RequestLogBenefit $requestLogBenefit) use ($requestLog) { return [ 'noklaim' => $requestLog->code, 'kodesubbenefit' => optional($requestLogBenefit->benefit)->code, 'namasubbenefit' => optional($requestLogBenefit->benefit)->description, 'kodebenefit' => optional($requestLog->plan)->code, 'namabenefit' => optional($requestLog->plan)->corporate_plan_id, 'biayaaju' => (float) ($requestLogBenefit->amount_incurred ?? 0), 'jaminanasuransi' => (float) ($requestLogBenefit->amount_approved ?? 0), 'jaminanpeserta' => (float) ($requestLogBenefit->excess_paid ?? 0), 'keterangan' => $requestLogBenefit->keterangan, ]; })->values(); $this->sendProviderOnlineEmail('pengesahan', $requestLog, $organization); return response()->json([ 'Status' => $this->okStatus(), 'Data' => [$this->buildClaimHeader($requestLog, $organization)], 'Biaya' => $biayaResponse, ]); } public function upsertBillingSementara(Request $request) { $request->validate([ 'noklaim' => 'required|string', 'kodeprovider' => 'required|string', 'tanggalkeluar' => 'nullable|date', 'kodediagnosa' => 'nullable|string', 'daftarbiaya' => 'nullable|array', 'daftarbiaya.*.kodesubbenefit' => 'required_with:daftarbiaya|string', 'daftarbiaya.*.biayaaju' => 'required_with:daftarbiaya|numeric', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $requestLog = RequestLog::query() ->where('code', $request->noklaim) ->where('organization_id', $organization->id) ->first(); if (!$requestLog) { return $this->statusError('Nomor klaim tidak ditemukan'); } $benefitsByCode = $this->resolveBenefitCodes($request->daftarbiaya ?? []); if (isset($benefitsByCode['error'])) { return $this->statusError($benefitsByCode['error']); } $requestLog->update([ 'discharge_date' => $request->tanggalkeluar, 'diagnosis' => $request->kodediagnosa, ]); foreach (($request->daftarbiaya ?? []) as $biaya) { $benefit = $benefitsByCode[$biaya['kodesubbenefit']]; RequestLogBenefit::query()->updateOrCreate( [ 'request_log_id' => $requestLog->id, 'benefit_id' => $benefit->id, ], [ 'amount_incurred' => $biaya['biayaaju'], 'amount_approved' => 0, 'amount_not_approved' => 0, 'excess_paid' => 0, 'keterangan' => null, ] ); } $this->sendProviderOnlineEmail('upsert billing sementara', $requestLog, $organization); return response()->json([ 'Status' => $this->okStatus(), ]); } public function getRincianBiayaKlaim(Request $request) { $request->validate([ 'noklaim' => 'required|string', 'kodeprovider' => 'required|string', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $requestLog = RequestLog::query() ->with(['member.person', 'member.currentCorporate', 'member.currentPolicy', 'plan', 'requestLogBenefits.benefit']) ->where('code', $request->noklaim) ->where('organization_id', $organization->id) ->first(); if (!$requestLog) { return $this->statusError('Nomor klaim tidak ditemukan'); } $biaya = $requestLog->requestLogBenefits->map(function (RequestLogBenefit $requestLogBenefit) use ($requestLog) { return [ 'noklaim' => $requestLog->code, 'kodesubbenefit' => optional($requestLogBenefit->benefit)->code, 'namasubbenefit' => optional($requestLogBenefit->benefit)->description, 'kodebenefit' => optional($requestLog->plan)->code, 'namabenefit' => optional($requestLog->plan)->corporate_plan_id, 'biayaaju' => (float) ($requestLogBenefit->amount_incurred ?? 0), 'jaminanasuransi' => (float) ($requestLogBenefit->amount_approved ?? 0), 'jaminanpeserta' => (float) ($requestLogBenefit->excess_paid ?? 0), 'keterangan' => $requestLogBenefit->keterangan, ]; })->values(); return response()->json([ 'Status' => $this->okStatus(), 'Data' => [$this->buildClaimHeader($requestLog, $organization)], 'Biaya' => $biaya, ]); } public function downloadStrukPendaftaran(Request $request) { $request->validate([ 'noklaim' => 'required|string', 'kodeprovider' => 'required|string', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $requestLog = RequestLog::query() ->where('code', $request->noklaim) ->where('organization_id', $organization->id) ->first(); if (!$requestLog) { return $this->statusError('Nomor klaim tidak ditemukan'); } try { return (new RequestLogController())->downlodLog($requestLog->id); } catch (Throwable $e) { $fallbackUrl = url('api/v1/hospitalportal/download-log/' . $requestLog->id); return $this->statusError( 'Gagal generate PDF struk pendaftaran: ' . $e->getMessage() . '. request_log_id=' . $requestLog->id . ', fallback_url=' . $fallbackUrl, 500 ); } } public function downloadStrukPengesahan(Request $request) { $request->validate([ 'noklaim' => 'required|string', 'kodeprovider' => 'required|string', 'p_user_no' => 'required', 'p_token' => 'required|string', ]); [, $organization, $authError] = $this->resolveProvider($request->kodeprovider, $request->p_user_no, $request->p_token); if ($authError) { return $authError; } $requestLog = RequestLog::query() ->where('code', $request->noklaim) ->where('organization_id', $organization->id) ->first(); if (!$requestLog) { return $this->statusError('Nomor klaim tidak ditemukan'); } try { return (new RequestLogController())->downlodFinalLog($requestLog->id); } catch (Throwable $e) { $fallbackUrl = url('api/v1/hospitalportal/download-final-log/' . $requestLog->id); return $this->statusError( 'Gagal generate PDF struk pengesahan: ' . $e->getMessage() . '. request_log_id=' . $requestLog->id . ', fallback_url=' . $fallbackUrl, 500 ); } } private function resolveProvider(string $kodeProvider, $userId, string $token): array { $organization = Organization::query()->where('code', $kodeProvider)->first(); if (!$organization) { return [null, null, $this->statusError('Kode provider tidak ditemukan')]; } $provider = Provider::query() ->where('id', $userId) ->where('organization_id', $organization->id) ->where('code', $kodeProvider) ->where('status', 'active') ->first(); if (!$provider) { return [null, null, $this->statusError('User provider tidak ditemukan')]; } if ((string) $provider->token !== (string) $token) { return [null, null, $this->statusError('Token tidak valid')]; } return [$provider, $organization, null]; } private function resolvePlan(Member $member, string $serviceCode): ?Plan { $memberPlan = $member->memberPlans() ->with('plan') ->where('status', 'active') ->orderByDesc('id') ->get() ->first(function (MemberPlan $item) use ($serviceCode) { return optional($item->plan)->service_code === $serviceCode; }); if ($memberPlan && $memberPlan->plan) { return $memberPlan->plan; } return optional($member->memberPlans()->with('plan')->orderByDesc('id')->first())->plan; } private function resolveBenefitCodes(array $daftarBiaya): array { $codes = collect($daftarBiaya) ->pluck('kodesubbenefit') ->filter() ->unique() ->values(); if ($codes->isEmpty()) { return []; } $benefits = Benefit::query() ->whereIn('code', $codes) ->get() ->keyBy('code'); $missing = $codes->filter(function (string $code) use ($benefits) { return !$benefits->has($code); }); if ($missing->isNotEmpty()) { return [ 'error' => 'Kode sub benefit tidak valid: ' . $missing->implode(', '), ]; } return $benefits->all(); } private function buildClaimHeader(RequestLog $requestLog, Organization $organization): array { $requestLog->loadMissing(['member.currentCorporate', 'member.currentPolicy', 'plan']); return [ 'noklaim' => $requestLog->code, 'namapeserta' => optional($requestLog->member)->full_name, 'tanggallahir' => $this->isoDate(optional($requestLog->member)->birth_date), 'nokartu' => optional($requestLog->member)->member_id, 'nopolis' => optional(optional($requestLog->member)->currentPolicy)->code, 'nobpjs' => optional($requestLog->member)->bpjs_id, 'nosep' => $requestLog->nomor_sep, 'nomorrujukan' => $requestLog->nomor_rujukan, 'planid' => optional($requestLog->plan)->code, 'masapolis' => optional(optional($requestLog->member)->currentPolicy)->end ? Carbon::parse($requestLog->member->currentPolicy->end)->format('d/m/Y') : null, 'namaperusahaan' => optional(optional($requestLog->member)->currentCorporate)->name, 'namapenjamin' => $organization->name, 'tanggalmasuk' => $this->isoDate($requestLog->admission_date), 'tanggalkeluar' => $this->isoDate($requestLog->discharge_date), 'hakkamar' => $requestLog->penempatan_kamar, 'hakicu' => null, 'nosuratjaminan' => $requestLog->code, 'namapegawai' => null, 'namabenefit' => optional($requestLog->plan)->corporate_plan_id, 'kodediagnosa' => $requestLog->diagnosis, 'keterangan' => $requestLog->keterangan, 'catatanTC1' => null, 'catatanTC2' => null, 'catatanTC3' => null, 'catatanTC4' => null, 'catatanTC5' => null, 'catatanTC6' => null, 'catatanTC7' => null, 'catatanTC8' => null, 'catatanTC9' => null, 'catatanTC10' => null, 'statusrujukan' => $requestLog->status_rujukan, 'statusklaim' => 0, 'notransaksiprovider' => $requestLog->no_transaksi_provider, 'inacbgscode' => $requestLog->inacbgs_code, 'inacbgsamount' => (float) ($requestLog->inacbgs_amount ?? 0), ]; } private function sendProviderOnlineEmail(string $eventName, RequestLog $requestLog, Organization $organization): void { $email = trim((string) ($organization->email ?: env('PROVIDER_ONLINE_NOTIFICATION_EMAIL', 'helpdesk@linksehat.com'))); if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) { return; } $name = $organization->name ?: 'Provider'; $subject = sprintf('[ProviderOnline] %s - %s', ucfirst($eventName), $requestLog->code); $body = sprintf( '
Halo %s,
Event %s berhasil diproses untuk nomor klaim %s.
Waktu: %s
', e($name), e($eventName), e($requestLog->code), now()->format('Y-m-d H:i:s') ); try { Helper::sendEmail([ 'email' => $email, 'name' => $name, 'subject' => $subject, 'body' => $body, ]); } catch (Throwable $throwable) { Log::warning('ProviderOnline notification email failed', [ 'event' => $eventName, 'request_log_id' => $requestLog->id, 'request_log_code' => $requestLog->code, 'organization_id' => $organization->id, 'error' => $throwable->getMessage(), ]); } } private function okStatus(): array { return [ 'errornumber' => 0, 'messagestring' => 'Success', ]; } private function statusError(string $message, int $statusCode = 400) { return response()->json([ 'Status' => [ 'errornumber' => 1, 'messagestring' => $message, ], 'Data' => null, ], $statusCode); } private function headerError(string $message, int $statusCode = 400) { return response()->json([ 'header-token' => null, 'userid' => 0, 'usertoken' => null, 'kodeprovider' => null, 'namaprovider' => null, 'errornumber' => 1, 'messagestring' => $message, ], $statusCode); } private function isoDate($date): ?string { if (empty($date)) { return null; } return Carbon::parse($date)->toISOString(); } private function generateNextRequestLogCode(Organization $organization, Member $member): string { $data = [ 'source' => 'H', 'provideCode' => $organization->code ?? '', 'date' => date('ymd'), 'policy' => optional($member->currentPolicy)->code ?? '-', 'member_code' => $member->member_id ?? '-', ]; $lastNumericCode = RequestLog::query() ->select(DB::raw('MAX(CAST(SUBSTRING_INDEX(code, ".", -1) AS SIGNED)) as max_numeric_code')) ->whereRaw('SUBSTRING_INDEX(code, ".", -1) REGEXP "^[0-9]+$"') ->value('max_numeric_code'); $nextNumber = $lastNumericCode ? ((int) $lastNumericCode + 1) : 1; return $this->makeRequestLogCode($nextNumber, $data); } private function makeRequestLogCode(int $nextNumber, array $data): string { $nextNumber = max(1, $nextNumber); return implode('.', [ 'LOG', $data['source'], $data['provideCode'], $data['date'], $data['policy'], $data['member_code'], str_pad((string) $nextNumber, 5, '0', STR_PAD_LEFT), ]); } }