diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index 5e7b94e1..0bf5f3bb 100644 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -66,5 +66,6 @@ Route::prefix('client')->group(function () { Route::get('claims/{id}', [ClaimController::class, 'show']); Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store'); + Route::post('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show'); }); }); diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php index 3a9ceaac..5765deea 100644 --- a/Modules/Internal/Http/Controllers/Api/ClaimController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -36,6 +36,7 @@ class ClaimController extends Controller 'claimRequest', 'claimRequest.service' ]) + ->where('status', '!=', 'requested') // penjagaan agar approve baru masuk ke claim management ->latest() ->paginate(10); diff --git a/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php b/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php index 4419dc9c..10e4ada3 100644 --- a/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php @@ -4,12 +4,18 @@ namespace Modules\Internal\Http\Controllers\Api; use App\Helpers\Helper; use App\Models\ClaimRequest; +use App\Models\Organization; use App\Services\ClaimService; +use App\Services\ImportService; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Modules\Internal\Transformers\ClaimRequestResource; use Modules\Internal\Transformers\ClaimRequestShowResource; +use Illuminate\Support\Facades\Storage; +use App\Services\ClaimRequestService; +use App\Exceptions\ImportRowException; + use App\Models\File; use App\Models\FilesMcu; @@ -24,6 +30,9 @@ class ClaimRequestController extends Controller $claimRequests = ClaimRequest::query() ->when($request->search, function ($q, $search) { $q->where('code', 'LIKE', "%".$search."%"); + $q->orWhereHas('member', function ($subQuery) use ($search) { + $subQuery->where('name', 'LIKE', "%".$search.""); + }); }) ->when($request->orderBy, function ($q, $orderBy) use ($request) { if (in_array($orderBy, ['submission_date', 'code'])) { @@ -73,7 +82,10 @@ class ClaimRequestController extends Controller 'histories' => function ($history) { $history->latest(); }, - 'files' + 'files', + 'member', + 'claim', + 'claim.organization', ]); return Helper::responseJson(data: ClaimRequestShowResource::make($claimRequest)); @@ -97,7 +109,68 @@ class ClaimRequestController extends Controller */ public function update(Request $request, $id) { - // + $claim_id = ClaimRequest::find($id)->claim_id; + $organization_id = Organization::where('code', $request->provider_code)->first(); + if (!$organization_id) { + return response()->json(['error' => true, 'message' => 'Data tidak ditemukan'], 404); + } + + $newClaimRequest = ClaimRequestService::updateClaimRequest(code: $code, member: $member, paymentType: 'reimbursement', serviceCode: $request->service_code); + + ClaimRequested::dispatch($newClaimRequest); + + // Log History + $newClaimRequest->histories()->create([ + 'title' => 'Update Claim Requested', + 'description' => "Update Claim Requested for Member : {$member->member_id} - ({$member->full_name})", + 'type' => 'info', + 'system_origin' => 'hospital-portal' + ]); + + if ($request->hasFile('result_files')) { + foreach ($request->result_files as $file) { + $pathFile = File::storeFile('claim-result', $newClaimRequest->id, $file); + $newClaimRequest->files()->updateOrCreate([ + 'type' => 'claim-result', + 'name' => File::getFileName('claim-result', $newClaimRequest->id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('diagnosa_files')) { + foreach ($request->diagnosa_files as $file) { + $pathFile = File::storeFile('claim-diagnosis', $newClaimRequest->id, $file); + $newClaimRequest->files()->updateOrCreate([ + 'type' => 'claim-diagnosis', + 'name' => File::getFileName('claim-diagnosis', $newClaimRequest->id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('kondisi_files')) { + foreach ($request->kondisi_files as $file) { + $pathFile = File::storeFile('claim-kondisi', $newClaimRequest->id, $file); + $newClaimRequest->files()->updateOrCreate([ + 'type' => 'claim-kondisi', + 'name' => File::getFileName('claim-kondisi', $newClaimRequest->id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } } /** @@ -167,4 +240,100 @@ class ClaimRequestController extends Controller } + public function importClaim(Request $request) + { + + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + $file_name = now()->getPreciseTimestamp(3) . '-' . $request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + $fileWrite = Storage::disk('public')->path('temp/result-' . $file_name); + $fileRead = Storage::path('temp/' . $file_name); + $import = new ImportService(); + $import->read($fileRead); + $import->write($fileWrite, 'xsls'); + foreach ($import->sheetsIterator() as $sheetIndex => $sheet) { + if ($sheetIndex == 1) { // Rename First Sheet to Writer + $firstWriterSheet = $import->writer->getCurrentSheet(); + $firstWriterSheet->setName($sheet->getName()); + } else { // Add New Sheet to Writer + $nextWriterSheet = $import->writer->addNewSheetAndMakeItCurrent(); + $nextWriterSheet->setName($sheet->getName()); + } + + $headers_map_to_table_fields = ClaimRequest::$doc_headers_to_field_map; + + // Write Header to File + $result_headers = array_keys($headers_map_to_table_fields); + $result_headers = array_merge($result_headers, ['Ingest Code', 'Ingest Note']); + + $import->addArrayToRow($result_headers); + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + $title = $cell->getValue(); + $title = preg_replace("/\r|\n/", " ", $title); + $title = preg_replace('/\xc2\xa0/', " ", $title); + $title = rtrim($title); + $title = ltrim($title); + $doc_headers_indexes[$index] = $title; + } + // TODO Validate if First Row not Header + } else { // Next Row Should be Data + $row_data = []; + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) + $row_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); + } + try { // Process the Row Data + $claimRequestService = new ClaimRequestService(); + + $claimRequestService->handleClaimRequestRow($row_data); + + // Write Success Result to File + // $import->read($fileRead); + // $import->write($fileWrite, 'xsls'); + $result_headers = array_merge($row_data, ['Ingest Code' =>200, 'Ingest Note' => 'Success']); + + $import->addArrayToRow($result_headers, $sheet->getName()); + + } catch (ImportRowException $e) { + // Write Data Validation Error to File + // $import->read($fileRead); + // $import->write($fileWrite, 'xsls'); + + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => $e->getCode(), + 'Ingest Note' => $e->getMessage(), + ]), $sheet->getName()); + } catch (\Exception $e) { + // throw new \Exception($e); + // Write Server Error to File + // $import->read($fileRead); + // $import->write($fileWrite, 'xsls'); + dd($e); + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => 500, + 'Ingest Note' => env('APP_DEBUG') ? $e->getMessage() : 'Server Error', + ]), $sheet->getName()); + } + } + } + } + $import->reader->close(); + Storage::delete('temp/' . $file_name); + $import->writer->close(); + + return [ + // 'total_successed_row' => $imported_plan_data, + // 'total_failed_row' => count($failed_plan_data), + // 'failed_row' => $failed_plan_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-' . $file_name), + 'name' => 'result-' . $file_name, + ] + ]; + } } diff --git a/Modules/Internal/Http/Controllers/Api/CorporateController.php b/Modules/Internal/Http/Controllers/Api/CorporateController.php index fb53bd26..0c077520 100644 --- a/Modules/Internal/Http/Controllers/Api/CorporateController.php +++ b/Modules/Internal/Http/Controllers/Api/CorporateController.php @@ -546,6 +546,12 @@ class CorporateController extends Controller "file_url" => url('files/Template - Formularium - Corporate.xlsx') ]); break; + case 'claim-request': + return Helper::responseJson([ + 'file_name' => "Template Format Claim.xlsx", + "file_url" => url('files/Template Format Claim.xlsx') + ]); + break; default: return Helper::responseJson([], 'error', 404); break; diff --git a/Modules/Internal/Http/Controllers/Api/DrugController.php b/Modules/Internal/Http/Controllers/Api/DrugController.php index 1402699e..2a25a575 100644 --- a/Modules/Internal/Http/Controllers/Api/DrugController.php +++ b/Modules/Internal/Http/Controllers/Api/DrugController.php @@ -6,6 +6,8 @@ use App\Models\Drug; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; +use App\Helpers\Helper; +use Maatwebsite\Excel\Facades\Excel; class DrugController extends Controller { @@ -15,8 +17,11 @@ class DrugController extends Controller */ public function index(Request $request) { - $drugs = Drug::withTrashed()->filter($request->toArray())->paginate(); - + $drugs = Drug::query() + ->filter($request->all()) + ->orderBy('id', 'DESC') + ->paginate(0) + ->appends($request->all()); return $drugs; } @@ -79,4 +84,110 @@ class DrugController extends Controller { // } + + public function activation(Request $request, $drug_id) + { + $request->validate([ + 'active' => 'required', + 'reason' => 'required', + ]); + + $drug = Drug::findOrFail($drug_id); + $drug->active = $request->active; + $drug->reason = $request->reason; + + if ($drug->save()) { + return response()->json([ + 'hostpital' => $drug, + 'message' => 'Status Updated Successfully' + ]); + } + } + public function downloadTemplate() + { + return Helper::responseJson([ + 'file_name' => "Template - Drugs.xlsx", + "file_url" => url('files/Template - Drugs.xlsx') + ]); + } + public function import(Request $request) + { + if ($request->hasFile('file')) { + $file = $request->file('file'); + $data = Excel::toArray([], $file); + + $processedData = $this->processCategoryNames($data); + + $importedRows = 0; + $failedRows = []; + + foreach ($processedData as $row) { + try { + Drug::create( + [ + 'name' => $row['name'], + 'code' => $row['code'], + 'generic_name' => $row['generic_name'], + 'description' => $row['description'], + 'mims_class' => $row['mims_class'], + 'indications' => $row['indications'], + 'atc_code' => $row['atc_code'], + 'segmentation' => $row['segmentation'], + 'type' => $row['type'], + 'dosage' => $row['dosage'], + 'remark' => $row['remark'], + ] + ); + $importedRows++; + } catch (\Exception $e) { + $failedRows[] = $row; + } + } + + $response = [ + 'message' => 'File uploaded and data saved to database!', + 'data' => [ + 'total_success_row' => $importedRows, + 'total_failed_row' => count($failedRows), + 'failed_rows' => $failedRows, + ], + ]; + + return response()->json($response); + } + + return response()->json(['error' => 'No file uploaded.']); + } + + private function processCategoryNames($data) + { + $header = []; + $row = []; + for ($i = 1; $i < count($data[0]); $i++) { + $row[] = $data[0][$i]; + $header[] = $data[0][0]; + } + + $filed = []; + foreach ($header[0] as $value) + { + $modelColumn = strtolower(preg_replace('/\s+/', '_', trim($value))); + $modelColumn = str_replace(['*', ' '], '', $modelColumn); + if($modelColumn) + { + $filed[] = $modelColumn; + } + } + + $result = []; + foreach ($row as $subarray) { + $trimmedSubarray = []; + for ($i = 0; $i < count($filed); $i++) { + $trimmedSubarray[$filed[$i]] = $subarray[$i] ? $subarray[$i] : null; + } + + $result[] = $trimmedSubarray; + } + return $result; + } } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 3485ed3a..71dddf4d 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -173,6 +173,9 @@ Route::prefix('internal')->group(function () { Route::put('master/diagnosis/{diagnosis_template_id}/activation', [DiagnosisController::class, 'activation']); Route::get('master/drugs', [DrugController::class, 'index']); + Route::put('master/drugs/{drug_id}/activation', [DrugController::class, 'activation']); + Route::get('master/drugs/download-template', [DrugController::class, 'downloadTemplate']); + Route::post('master/drugs/import', [DrugController::class, 'import']); Route::get('members', [MemberController::class, 'index']); @@ -213,6 +216,8 @@ Route::prefix('internal')->group(function () { Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index'); Route::post('claim-requests/{id}/approve', [ClaimRequestController::class, 'approve'])->name('claim-requests.approve'); Route::get('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show'); + Route::put('claim-requests/{id}', [ClaimRequestController::class, 'update'])->name('claim-requests.update'); + Route::post('claim-requests/import', [ClaimRequestController::class, 'importClaim'])->name('claim-requests.importClaim'); }); Route::get('province', [ProvinceController::class, 'index']); diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 7ae4d707..465090af 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -216,5 +216,10 @@ class Helper return $sPaymentMethod[$id]; } + public static function formatDateDB($date){ + $convertedDate = Carbon::createFromFormat('d-m-Y', $date)->format('Y-m-d H:i:s'); + return $convertedDate; + } + } diff --git a/app/Models/Claim.php b/app/Models/Claim.php index 84161e59..a1710e5a 100644 --- a/app/Models/Claim.php +++ b/app/Models/Claim.php @@ -29,6 +29,7 @@ class Claim extends Model 'currency', 'plan_id', 'benefit_id', + 'organization_id', 'status', 'service_code' ]; @@ -196,6 +197,11 @@ class Claim extends Model return $this->belongsTo(Member::class, 'member_id'); } + public function organization() + { + return $this->belongsTo(Organization::class, 'organization_id'); + } + public function encounters() { return $this->belongsToMany(Encounter::class, 'claim_encounter'); diff --git a/app/Models/ClaimRequest.php b/app/Models/ClaimRequest.php index d1b7772a..74ad84ec 100644 --- a/app/Models/ClaimRequest.php +++ b/app/Models/ClaimRequest.php @@ -38,6 +38,71 @@ class ClaimRequest extends Model 'deleted_by', ]; + public static $doc_headers_to_field_map = [ + "PAYOR ID" => "payor_id", + "CORPORATE ID" => "corporate_id", + "POLICY NUMBER" => "policy_number", + "MEMBER ID" => "member_id", + "MEMBER NAME" => "member_name", + "RECORD TYPE (P/D)" => "record_type", + "CLAIM TYPE" => "claim_type", + "CLAIM PROCESS STATUS" => "status", + "CLIENT CLAIM ID" => "client_claim_id", + "REFERENCE NO" => "reference_no", + "ADMEDIKA CLAIM ID" => "admika_claim_id", + "PROVIDER CODE" => "provider_code", + "ADMISSION DATE" => "admission_date", + "DISCUTRGE DATE" => "discutrge_date", + "DURATION DAYS" => "duration_days", + "COVERAGE TYPE" => "coverage_type", + "PLAN ID" => "plan_id", + "DIAGNOSIS CODE" => "diagnosis_code", + "DIAGNOSIS DESC" => "diagnosis_desc", + "TOT AMT INCURRED" => "tot_amt_insurred", + "TOT AMT APPROVED" => "tot_amt_approved", + "TOT AMT NOT APPROVED" => "tot_amt_not_approved", + "TOT EXCESS PAID" => "tot_excess_paid", + "REMARKS" => "remarks", + "SECONDARY DIAGNOSIS CODE" => "secondary_diagnosis_code", + "APPROVED DATE" => "approved_date", + "APPROVED BY" => "approved_by", + "DATE RECEIVED" => "data_received", + "HOSPITAL INVOICE DATE" => "hospital_invoice_date", + ]; + + public static $listing_doc_headers = [ + "PAYOR ID", + "CORPORATE ID", + "POLICY NUMBER", + "MEMBER ID", + "MEMBER NAME", + "RECORD TYPE (P/D)", + "CLAIM TYPE", + "CLAIM PROCESS STATUS", + "CLIENT CLAIM ID", + "REFERENCE NO", + "ADMEDIKA CLAIM ID", + "PROVIDER CODE", + "ADMISSION DATE", + "DISCUTRGE DATE", + "DURATION DAYS", + "COVERAGE TYPE", + "PLAN ID", + "DIAGNOSIS CODE", + "DIAGNOSIS DESC", + "TOT AMT INCURRED", + "TOT AMT APPROVED", + "TOT AMT NOT APPROVED", + "TOT EXCESS PAID", + "REMARKS", + "SECONDARY DIAGNOSIS CODE", + "APPROVED DATE", + "APPROVED BY", + "DATE RECEIVED", + "HOSPITAL INVOICE DATE", + ]; + + public static $status = [ 'draft' => 'Draft', 'requested' => 'Requested', diff --git a/app/Models/Drug.php b/app/Models/Drug.php index ad74b58a..a1fb9655 100644 --- a/app/Models/Drug.php +++ b/app/Models/Drug.php @@ -25,7 +25,8 @@ class Drug extends Model 'dosage', 'remark', 'selling_unit_id', - 'status' + 'status', + 'active', ]; public function categories() diff --git a/app/Models/Organization.php b/app/Models/Organization.php index 107a4125..31c920b1 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -98,4 +98,9 @@ class Organization extends Model { return $this->hasMany(PractitionerRole::class, 'organization_id'); } + + public function claims() + { + return $this->hasMany(Claim::class, 'organization_id', 'id'); + } } diff --git a/app/Services/ClaimRequestService.php b/app/Services/ClaimRequestService.php index 2e5428d4..07b79a59 100644 --- a/app/Services/ClaimRequestService.php +++ b/app/Services/ClaimRequestService.php @@ -6,9 +6,16 @@ use App\Events\ClaimApproved; use App\Events\ClaimRequested; use App\Models\Claim; use App\Models\ClaimRequest; +use App\Models\Organization; +use App\Helpers\Helper; use App\Models\Icd; use App\Models\Member; use Carbon\Carbon; + +use App\Exceptions\ImportRowException; +use Box\Spout\Writer\Common\Creator\WriterEntityFactory; + + use DB; use Str; @@ -32,6 +39,7 @@ class ClaimRequestService{ $claimRequest = ClaimRequest::create($claimRequestData); DB::commit(); + return $claimRequest; } catch (\Exception $error) { DB::rollBack(); @@ -40,4 +48,114 @@ class ClaimRequestService{ } } + public static function storeClaimManagement($row, $member, $claim_request_id){ + try { + $organization = 0; + if($row['provider_code']){ + $organization = Organization::where('code', $row['provider_code'])->first(); + if (!$organization){ + throw new ImportRowException(__('Provider Tidak ditemukan'), 0, null, $row); + } + }; + if(!$member){ + throw new ImportRowException(__('Member Tidak ditemukan'), 0, null, $row); + }; + DB::beginTransaction(); + $data = [ + 'member_id' => $member->id, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'total_claim' => $row['tot_amt_insurred'], + 'claim_request_id' => $claim_request_id, + 'organization_id' => $organization ? $organization->id : NULL, + 'status' => 'requested' + ]; + + + $claimManagement = Claim::create($data); + + // update client id di claim request + ClaimRequest::where('id', $claim_request_id)->update([ + 'claim_id' => $claimManagement->id, + ]); + + DB::commit(); + return $claimManagement; + + + } catch (\Exception $error) { + DB::rollBack(); + + throw new \Exception($error); + } + } + + public static function updateClaimRequest(){ + try { + $data = [ + 'member_id' => $member->id, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'total_claim' => $row['tot_amt_insurred'], + 'claim_request_id' => $claim_request_id, + 'organization_id' => $organization ? $organization->id : NULL, + 'status' => 'requested' + ]; + + } catch (\Exception $error) { + DB::rollBack(); + + throw new \Exception($error); + } + } + + protected function validatePlanRow($row) + { + if (empty($row['member_id'])) { + throw new ImportRowException(__('Member ID Required'), 0, null, $row); + } + } + + public function handleClaimRequestRow($row) + { + try { + $member = Member::where('member_id', $row['member_id'])->with(['currentPlan'])->first(); + if(!$member){ + throw new ImportRowException(__('Member Tidak ditemukan'), 0, null, $row); + }; + $code = $row['client_claim_id']; + $submissionDate = Helper::formatDateDB($row['admission_date']); + $paymentType = $row['claim_type']; + $status = $row['status']; + $serviceCode = $row['coverage_type']; + + $newClaimRequest = $this->storeClaimRequest(code: $code, member: $member, paymentType: $paymentType, serviceCode: $serviceCode, submissionDate: $submissionDate, status: $status); + + $newlyCreatedID = $newClaimRequest->id; + + $newClaimManangement = $this->storeClaimManagement($row, $member, $newlyCreatedID); + ClaimRequested::dispatch($newClaimRequest); + // Log History + $newClaimRequest->histories()->create([ + 'title' => 'New Claim Requested', + 'description' => "Claim Requested for Member : {$member->member_id} - ({$member->full_name})", + 'type' => 'info', + 'system_origin' => 'import-internal-aso' + ]); + + $claim_request_data = $row; + + // dd($claim_request_data['admission_date']); + + $this->validatePlanRow($claim_request_data); + + + + + return $newClaimRequest; + } catch (\Exception $e) { + throw $e; + } + } + } \ No newline at end of file diff --git a/database/migrations/2023_10_23_114257_add_column_active_to_drugs.php b/database/migrations/2023_10_23_114257_add_column_active_to_drugs.php new file mode 100644 index 00000000..04b9f3b0 --- /dev/null +++ b/database/migrations/2023_10_23_114257_add_column_active_to_drugs.php @@ -0,0 +1,34 @@ +text('reason')->nullable()->after('status'); + $table->tinyInteger('active')->default(1)->after('reason'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('drugs', function (Blueprint $table) { + $table->dropColumn('reason'); + $table->dropColumn('active'); + }); + } +}; diff --git a/database/migrations/2023_10_24_130726_add_column_organizations_to_claim.php b/database/migrations/2023_10_24_130726_add_column_organizations_to_claim.php new file mode 100644 index 00000000..d1c8d5e8 --- /dev/null +++ b/database/migrations/2023_10_24_130726_add_column_organizations_to_claim.php @@ -0,0 +1,32 @@ +integer('organization_id')->after('benefit_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('claims', function (Blueprint $table) { + $table->dropColumn('organization_id'); + }); + } +}; diff --git a/frontend/dashboard/src/@types/claims.ts b/frontend/dashboard/src/@types/claims.ts new file mode 100644 index 00000000..e75a6587 --- /dev/null +++ b/frontend/dashboard/src/@types/claims.ts @@ -0,0 +1,49 @@ +import { Member } from "./member"; + +export type ClaimRequest = { + id: number; + code: string; + name: string; + submission_date: string; + payment_type: string; + service_code: string; + claim_method: string; + service_type: string; + code_provider: string; + file_condition: Files; + member: Member; + claim: { + organization: Organizations + } + }; + +export type Files = { + name: string; + url: string; + path: string; +} + +export type Organizations = { + id: number; + code: string; + name: string; + address: string; + type: string; + lat: string; + lng: string; + phone: string; + timezone: string; + active: boolean | number; + province_id: number; + city_id: number; + district_id: number; + village_id: number; + postal_code: string; + description: string; + technology: string; + support_services: string; + merchant_code: string; + merchant_key: string; + image_url: string; + region_groups: string; +}; \ No newline at end of file diff --git a/frontend/dashboard/src/@types/corporates.ts b/frontend/dashboard/src/@types/corporates.ts index 61d35871..ff470d0f 100644 --- a/frontend/dashboard/src/@types/corporates.ts +++ b/frontend/dashboard/src/@types/corporates.ts @@ -30,6 +30,7 @@ export type Hospital = { corporate_id: number; code: string; name?: string; + active: number; } export type Employee = { diff --git a/frontend/dashboard/src/@types/pharmacy-and-delivery-managements.ts b/frontend/dashboard/src/@types/pharmacy-and-delivery-managements.ts new file mode 100644 index 00000000..661f4a46 --- /dev/null +++ b/frontend/dashboard/src/@types/pharmacy-and-delivery-managements.ts @@ -0,0 +1,8 @@ +export type Drug = { + id: number; + type: string; + code: string; + name: string; + version:string; + active: number; +} \ No newline at end of file diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index 90ac94b9..6ecadc09 100644 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -42,6 +42,7 @@ const navConfig = [ { title: 'PHARMACY & DELIVERY MANAGEMENT', children: [ + { title: 'Drug', path: '/master/drugs'}, { title: 'Inventory', path: '/inventory' }, { title: 'Delivery Services', path: '/delivery' }, ], @@ -53,7 +54,6 @@ const navConfig = [ { title: 'Corporate', path: '/corporates' }, // { title: 'Corporate Create', path: '/corporates/create' }, { title: 'Formularium', path: '/master/formularium-template' }, - { title: 'Obat', path: '/master/drugs' }, { title: 'Master ICD-10 Diagnosis', path: '/master/diagnosis-template' }, { title: 'Hospitals', path: '/hospitals' }, ], diff --git a/frontend/dashboard/src/pages/ClaimRequests/CreateUpdate.tsx b/frontend/dashboard/src/pages/ClaimRequests/CreateUpdate.tsx index 58bb43d6..12e5885d 100644 --- a/frontend/dashboard/src/pages/ClaimRequests/CreateUpdate.tsx +++ b/frontend/dashboard/src/pages/ClaimRequests/CreateUpdate.tsx @@ -15,44 +15,42 @@ import { enqueueSnackbar } from 'notistack'; import { LoadingButton } from '@mui/lab'; import { fCurrency } from '../../utils/formatNumber'; import Iconify from '../../components/Iconify'; +import { ClaimRequest } from '@/@types/claims'; import Form from './Form'; export default function ClaimsCreateUpdate() { + const { themeStretch } = useSettings(); const { id } = useParams(); const isEdit = id ? true : false; - const [currentClaim, setCurrentClaim] = useState(); + const [currentClaim, setCurrentClaim] = useState(); useEffect(() => { if (isEdit) { - axios.get('/claims/' + id).then((res) => { - // console.log('Yeet', res.data); - setCurrentClaim(res.data); + axios.get('/claim-requests/' + id).then((res) => { + console.log('Yeet', res.data); + setCurrentClaim(res.data.data); }); + + console.log(currentClaim) } }, [id]); return ( - + diff --git a/frontend/dashboard/src/pages/ClaimRequests/Form.tsx b/frontend/dashboard/src/pages/ClaimRequests/Form.tsx index a4898695..d9428776 100644 --- a/frontend/dashboard/src/pages/ClaimRequests/Form.tsx +++ b/frontend/dashboard/src/pages/ClaimRequests/Form.tsx @@ -3,7 +3,7 @@ import { useSnackbar } from 'notistack'; import { useNavigate } from 'react-router-dom'; import { yupResolver } from '@hookform/resolvers/yup'; import { Controller, useForm } from 'react-hook-form'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useRef, useEffect, useMemo, useState } from 'react'; import axios from '../../utils/axios'; import { FormProvider, RHFTextField } from '../../components/hook-form'; import { @@ -24,16 +24,29 @@ import { ListItemAvatar, Avatar, ListItemText, + Card, + InputAdornment, + Divider, + ButtonBase, + Box, } from '@mui/material'; import Iconify from '../../components/Iconify'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; import { LoadingButton } from '@mui/lab'; import { fCurrency } from '../../utils/formatNumber'; import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; import { Add, DeleteOutline } from '@mui/icons-material'; +import { ClaimRequest, Files } from '@/@types/claims'; +import { fDateTimesecond } from '@/utils/formatTime'; + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} type Props = { isEdit: boolean; - currentClaim?: any; + currentClaim?: ClaimRequest; }; export default function ClaimForm({ isEdit, currentClaim }: Props) { @@ -41,28 +54,39 @@ export default function ClaimForm({ isEdit, currentClaim }: Props) { const { enqueueSnackbar } = useSnackbar(); - const NewCorporateSchema = Yup.object().shape({ - name: Yup.string().required('Name is required'), - code: Yup.string().required('Corporate Code is required'), - active: Yup.boolean().required('Corporate Status is required'), - // file: Yup.boolean().required('Corporate Status is required'), + const EditClaimSchema = Yup.object().shape({ + organization_id: Yup.string().required('Name is required'), }); const defaultValues = useMemo( () => ({ - member: currentClaim?.member || {}, - member_id: currentClaim?.member_id || null, - diagnosis_id: currentClaim?.diagnosis_id || null, - total_claim: currentClaim?.total_claim || 0, + id: currentClaim?.id || '-', + code: currentClaim?.code || '-', + member_name: currentClaim?.member?.name || '-', + date: currentClaim?.submission_date ? fDateTimesecond(currentClaim?.submission_date) : '-', + claim_method: currentClaim?.payment_type || '-', + service_type: currentClaim?.service_code || '-', + organization_id: currentClaim?.claim?.organization?.code || '-', }), - // eslint-disable-next-line react-hooks/exhaustive-deps [currentClaim] ); - const methods = useForm({ - resolver: yupResolver(NewCorporateSchema), + useEffect(() => { + console.log(currentClaim, 'er') + if (isEdit && currentClaim) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + }, [isEdit, currentClaim]); + + + const methods = useForm({ + resolver: yupResolver(EditClaimSchema), defaultValues, }); + const { reset, watch, @@ -83,514 +107,326 @@ export default function ClaimForm({ isEdit, currentClaim }: Props) { const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); const [member, setMember] = useState({}) - - useEffect(() => { - console.log('defaultValues', defaultValues); - if (isEdit && currentClaim) { - reset(defaultValues); - setMember(defaultValues.member) + // ---------------------------------------------------------------------- + + // Files Result Kondisi + const fileKondisiInput = useRef(null); + const [fileKondisis, setFileKondisis] = useState([]); + + const handleKondisiInputChange = (event) => { + if (event.target.files[0]) { + setFileKondisis([...fileKondisis, ...event.target.files]); + } else { + console.log('NO FILE'); } - if (!isEdit) { - reset(defaultValues); - setMember(defaultValues.member) + }; + const removeKondisiFiles = (filesState, index) => { + setFileKondisis( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + // Files Result Diagnosa + const fileDiagnosaInput = useRef(null); + const [fileDiagnosas, setFileDiagnosas] = useState([]); + + const handleDiagnosaInputChange = (event) => { + if (event.target.files[0]) { + setFileDiagnosas([...fileDiagnosas, ...event.target.files]); + } else { + console.log('NO FILE'); } - }, [isEdit, currentClaim]); - - const fileSelected = (event, type) => { - const files = event.target.files; - const currentFiles = getValues(`uploaded_files.${type}`) ?? []; - - setValue(`uploaded_files.${type}`, [...currentFiles, ...files]); - - console.log('currentFiles', getValues('uploaded_files')); + }; + const removeDiagnosaFiles = (filesState, index) => { + setFileDiagnosas( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); }; - const memberSelected = (member) => { - setMember(member) + // Files Result Hasil Penunjang + const fileHasilPenunjangInput = useRef(null); + const [fileHasilPenunjangs, setFileHasilPenunjangs] = useState([]); + + const handleResultInputChange = (event) => { + if (event.target.files[0]) { + setFileHasilPenunjangs([...fileHasilPenunjangs, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeFiles = (filesState, index) => { + setFileHasilPenunjangs( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); }; - const checkLimit = async () => { - console.log('CHECKING LIMIT'); - }; - const onSubmit = async (data: any) => { + const onSubmit = async (data: FormValuesProps) => { try { - if (!isEdit) { - const response = await axios.post('/claims', data); - } else { - const response = await axios.put('/claims/' + currentClaim?.id ?? '', data); - } + const formData = new FormData(); + formData.append('result_files', fileHasilPenunjangs); + formData.append('diagnosa_files', fileDiagnosaInput); + formData.append('kondisi_files', fileKondisiInput); + formData.append('provider_code', data.organization_id); + formData.append('_method', 'PUT'); + + const response = await axios.post(`/claim-requests/${data.id}`, formData); + reset(); - enqueueSnackbar( - !isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!', - { variant: 'success' } - ); - navigate('/claims'); + enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' }); + navigate('/claim-requests'); } catch (error: any) { if (error && error.response.status === 422) { for (const [key, value] of Object.entries(error.response.data.errors)) { - setError(key, { message: value[0] }); - enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + // setError(key, { message: value[0] }); + enqueueSnackbar('Failed Processing Request', { variant: 'error' }); } } else { enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); } } - - const ascent = document?.querySelector('ascent'); - if (ascent != null) { - ascent.innerHTML = ''; - } }; - function generate(files, element: React.ReactElement) { - return files.map((value) => - React.cloneElement(element, { - key: value, - }) - ); - } - const headStyle = {}; return ( - - Member - - - - { - if (!isEdit) setIsMemberDialogOpen(true); - if (isEdit) enqueueSnackbar('Cannot Change Member', { variant: 'error' }); - }} - /> + + + + Code* - {/* - - */} - + + Name* + + + + + + + + {/* */} - {member?.id && ( - - - - - - - - Name - - {member?.full_name} - - - - DOB - - - {member?.birth_date} ({member?.age + ' years'}) - - - - - Marital Status - - {member?.marital_status} - - - - Record Type - - {member?.record_type} - - - - Principal ID - - - {member?.principal_id} ( - {member?.relation_with_principal}) - - - -
-
- - - - - - Plan - - {member?.current_plan?.code} - - - - Active - - - {member?.current_plan?.start} -{' '} - {member?.current_plan?.end} (Active) - - - - - Corporate Limit - - - {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} - - - - - Plan Usage - - - {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} - - - -
-
-
-
- )} + + - ( - - option ? `#${option.id} (${option.code}) ${option.description}` : '' - } - value={value || ''} - onChange={(event: any, newValue: any) => { - setValue('benefit_id', newValue?.id); - onChange(newValue); - }} - renderInput={(params) => ( - { - // if (event.key === 'Enter') - // searchDiagnosis(event.target.value) - // }} - /> - )} - /> - )} - /> + + Date of Submission* + + + Claim Method* + + + Service Type* + + + Code Provider* + - ( - (option ? `(${option.code}) ${option.name}` : '')} - value={value || ''} - onChange={(event: any, newValue: any) => { - setValue('diagnosis_id', newValue?.id); - // setValue('diagnosis', newValue) - onChange(newValue); - }} - renderInput={(params) => ( - { - if (event.key === 'Enter') searchDiagnosis(event.target.value); - }} - /> - )} - /> - )} - /> - {isCheckingLimit && ( - - {/* Checking */} - - - - Please Wait, Checking Eligibilty - - - - )} - {false && isCheckingLimit == false && isEligible == null && ( - - {/* No Data Selected */} - - - - Please Select Diagnosis ! - - - - )} - {!isCheckingLimit && isEligible !== null && isEligible && ( - - {/* Eligible */} - - - - Diagnosis is Eligible - - - - 125.000.000 / 125.000.000 - - - )} - {!isCheckingLimit && isEligible !== null && !isEligible && ( - - {/* Not Eligible */} - {/* - - - Not Eligible - - - - 125.000.000 / 125.000.000 - */} - - )} + + + + + ), }} + name="date" label="Date of Submission" disabled/> + + + + + + + + + + - - {isEdit && ( + {/* -------------------------------Upload Dokumen Kondisi------------------------------- */} - Documents - - - {(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length - ? getValues('uploaded_files.invoice') - : [] - ).map((file, index) => ( - - - - } - > - - - {/* */} - I - - - - + + Condition Document + + + {fileKondisis && + fileKondisis.map((file, index) => ( + + {file.name} + { + removeKondisiFiles(fileKondisis, index); + }} + > + ))} - - - - {(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length - ? getValues('uploaded_files.prescription') - : [] - ).map((file, index) => ( - - - - } - > - - - {/* */} - P - - - - - ))} - - - - - {(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length - ? getValues('uploaded_files.diagnosis') - : [] - ).map((file, index) => ( - - - - } - > - - - {/* */} - DR - - - - - ))} - - + + + fileKondisiInput.current?.click()}> + + + + Add File + + + + + + + {/* -------------------------------Upload Dokumen Diagnosa------------------------------- */} + + + Diagnosis Document + + + {fileDiagnosas && + fileDiagnosas.map((file, index) => ( + + {file.name} + { + removeDiagnosaFiles(fileDiagnosas, index); + }} + > + + ))} + + + fileDiagnosaInput.current?.click()}> + + + + Add Result + + + + + + + {/* -------------------------------Upload Result Hasil Penunjang------------------------------- */} + + + Supporting Result Document + + + {fileHasilPenunjangs && + fileHasilPenunjangs.map((file, index) => ( + + {file.name} + { + removeFiles(fileHasilPenunjangs, index); + }} + > + + ))} + + + fileHasilPenunjangInput.current?.click()}> + + + + Add Result + + + + + - )} - {isEligible === true ? ( - - Create Claim - - ) : ( - - Check Limit - - )} -
- - + + + + + + + + Update + + + +
); } diff --git a/frontend/dashboard/src/pages/ClaimRequests/List.tsx b/frontend/dashboard/src/pages/ClaimRequests/List.tsx index 1b597e0c..576ad6fa 100644 --- a/frontend/dashboard/src/pages/ClaimRequests/List.tsx +++ b/frontend/dashboard/src/pages/ClaimRequests/List.tsx @@ -17,12 +17,17 @@ import { ButtonGroup, Link, Chip, + TableHead, + Grid, } from '@mui/material'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import AddIcon from '@mui/icons-material/Add'; import UploadIcon from '@mui/icons-material/Upload'; import CancelIcon from '@mui/icons-material/Cancel'; + +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; // hooks import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; @@ -38,6 +43,10 @@ import { enqueueSnackbar } from 'notistack'; import { Divider } from '@mui/material'; import Iconify from '@/components/Iconify'; import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { capitalizeFirstLetter } from '@/utils/formatString'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; // import LoadingButton from '@/theme/overrides/LoadingButton'; export default function List() { @@ -45,6 +54,8 @@ export default function List() { const [searchParams, setSearchParams] = useSearchParams(); const [importResult, setImportResult] = useState(null); + const navigate = useNavigate() + function SearchInput(props: any) { // SEARCH const searchInput = useRef(null); @@ -75,6 +86,7 @@ export default function List() { fullWidth onChange={handleSearchChange} value={searchText} + placeholder='Search Code or Name...' /> ); @@ -87,26 +99,164 @@ export default function List() { const createMenu = Boolean(anchorEl); const importForm = useRef(null); const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; const handleClose = () => { setAnchorEl(null); }; + const handleImportButton = () => { + if (importForm?.current) { + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + + const handleCancelImportButton = () => { + importForm.current.value = ''; + importForm.current.dispatchEvent(new Event('change', { bubbles: true })); + }; + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]); + + setImportLoading(true); + axios + .post(`claim-requests/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + setImportLoading(false); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + }; + + const handleGetTemplate = (type :string) => { + axios.get('corporates/import-document-example/' + type) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + + const handleGetData = (type :string) => { + axios.get(`corporates/${corporate_id}/data-plan-benefit`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + return (
- - - {/* */} - + + {!currentImportFileName && ( + + + + + Import + {handleGetTemplate('claim-request')}}>Download Template test + {handleGetData('data-plan-benefit')}}>Download Claim Request + + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )}
); } @@ -176,43 +326,43 @@ export default function List() { return ( *': { borderBottom: 'unset' } }}> - + {/* setOpen(!open)}> {open ? : } - + */ } { - handleShowClaim(row); - }} - color={themeColorPresets} + // onClick={() => { + // handleShowClaim(row); + // }} > {row.code} {row.member?.full_name} - {row.member?.current_policy?.code} - {row.submission_date} + {row.service_name} - {/* {row.payment_type_name} */} - {'-'} - - + {row.payment_type_name} + + { row.status == "requested" ? + () : + () + } - {row.status == 'requested' && ( - { - handleApprove(row); - }} - > - Ajukan Claim - - )} + + navigate(`/claim-requests/edit/${row.id}`)}> + + Edit + + ''}> + + Detail + + + } /> {/* @@ -306,9 +456,9 @@ export default function List() { return ( {/* ------------------ TABLE HEADER ------------------ */} - + - + {/* */} Code @@ -316,23 +466,20 @@ export default function List() { Name - Nomor Polis - - - Tanggal Pengajuan + Date of Submission Service Type - Claim Type + Claim Method Status - + {/* ------------------ END TABLE HEADER ------------------ */} {/* ------------------ TABLE ROW ------------------ */} @@ -388,23 +535,28 @@ export default function List() { function handleDownloadLog() {} return ( - - + + + + - } - /> - - - + + } + /> + + + + + ); } diff --git a/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx b/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx index 74f4bc94..1ad86b41 100644 --- a/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Hospital/List.tsx @@ -27,7 +27,7 @@ import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; // components import axios from '../../../utils/axios'; -import { CorporatePlan } from '../../../@types/corporates'; +import { Hospital } from '../../../@types/corporates'; import { LaravelPaginatedData } from '../../../@types/paginated-data'; import BasePagination from '../../../components/BasePagination'; import TableMoreMenu from '@/components/table/TableMoreMenu'; @@ -40,7 +40,7 @@ import CloseIcon from '@mui/icons-material/Close'; import { enqueueSnackbar } from 'notistack'; import Label from '../../../components/Label'; -export default function PlanList() { +export default function HospitalList() { const { corporate_id } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); @@ -80,9 +80,9 @@ export default function PlanList() { } // Called on every row to map the data to the columns - function createData(plan: CorporatePlan): CorporatePlan { + function createData(hospital: Hospital): Hospital { return { - ...plan, + ...hospital, }; } diff --git a/frontend/dashboard/src/pages/Corporates/Member/List.tsx b/frontend/dashboard/src/pages/Corporates/Member/List.tsx index 35e208ca..9b6114ec 100644 --- a/frontend/dashboard/src/pages/Corporates/Member/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Member/List.tsx @@ -61,6 +61,7 @@ import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; +import Label from '../../../components/Label'; export default function CorporatePlanList({handleSubmitSuccess}) { const navigate = useNavigate(); @@ -424,12 +425,14 @@ export default function CorporatePlanList({handleSubmitSuccess}) { } const [columns, setColumns] = React.useState([ - { id: 'member_id', label: 'Member ID', minWidth: 100, align: 'left', width: '10%' }, - { id: 'effective_date', label: 'Effective Date', minWidth: 100, align: 'left', width: '20%' }, + { id: 'member_id', label: 'Member ID', minWidth: 100, align: 'left', width: '15%' }, + { id: 'effective_date', label: 'Effective Date', minWidth: 100, align: 'left', width: '15%' }, { id: 'name', label: 'Name', minWidth: 100, align: 'left', width: '20%' }, - { id: 'plan_id', label: 'Plan ID', minWidth: 100, align: 'left', width: '10%' }, - { id: 'activation_date', label: 'Activation Date', minWidth: 100, align: 'left', width: '20%' }, - { id: 'termination_date', label: 'Termination Date', minWidth: 100, align: 'left', width: '20%' }, + { id: 'plan_id', label: 'Plan', minWidth: 100, align: 'left', width: '10%' }, + { id: 'activation_date', label: 'Activation Date', minWidth: 100, align: 'left', width: '15%' }, + { id: 'termination_date', label: 'Termination Date', minWidth: 100, align: 'left', width: '15%' }, + {id: 'status', label: 'Status', minWidth: 100, align: 'left', width: '5%' }, + {id: 'action', label: '', minWidth: 100, align: 'left', width: '5%' }, ]); // Generate the every row of the table @@ -489,6 +492,19 @@ export default function CorporatePlanList({handleSubmitSuccess}) { {row.terminated_date ? row.terminated_date : '-'} + + + {row.active === 1 ? ( + + ) : ( + + )} + + diff --git a/frontend/dashboard/src/pages/Master/Drug/Index.tsx b/frontend/dashboard/src/pages/Master/Drug/Index.tsx index 0bdff569..91393b10 100644 --- a/frontend/dashboard/src/pages/Master/Drug/Index.tsx +++ b/frontend/dashboard/src/pages/Master/Drug/Index.tsx @@ -1,16 +1,11 @@ -import { Card, Grid } from "@mui/material"; -import { useParams } from "react-router-dom"; +import { Card } from "@mui/material"; import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; import Page from "../../../components/Page"; -import useSettings from "../../../hooks/useSettings"; import List from "./List"; export default function Drugs() { - const { themeStretch } = useSettings(); - - const { corporate_id } = useParams(); const pageTitle = 'Drug'; return ( @@ -20,8 +15,8 @@ export default function Drugs() { heading={ pageTitle } links={[ { - name: 'Master', - href: '/master', + name: 'Pharmacy & Delivery Management', + href: '/', }, { name: 'Drug', diff --git a/frontend/dashboard/src/pages/Master/Drug/List.tsx b/frontend/dashboard/src/pages/Master/Drug/List.tsx index ae0a4abd..2a2e5f22 100644 --- a/frontend/dashboard/src/pages/Master/Drug/List.tsx +++ b/frontend/dashboard/src/pages/Master/Drug/List.tsx @@ -1,310 +1,579 @@ // @mui -import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; -import AddIcon from '@mui/icons-material/Add'; -import UploadIcon from '@mui/icons-material/Upload'; -import CancelIcon from '@mui/icons-material/Cancel'; -// hooks -import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; -import useSettings from '../../../hooks/useSettings'; -import { useParams, useSearchParams } from 'react-router-dom'; -// components -import axios from '../../../utils/axios'; -import { LaravelPaginatedData } from '../../../@types/paginated-data'; -import { Icd } from '../../../@types/diagnosis'; -import BasePagination from '../../../components/BasePagination'; +import { + Button, + Card, + IconButton, + MenuItem, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Stack, + Collapse, + Box, + FormControl, + InputLabel, + Select, + FormHelperText, + Menu, + ButtonGroup, + } from '@mui/material'; + import AddIcon from '@mui/icons-material/Add'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; + // components + import axios from '../../../utils/axios'; + import { Drug } from '../../../@types/pharmacy-and-delivery-managements'; + import { LaravelPaginatedData } from '../../../@types/paginated-data'; + import BasePagination from '../../../components/BasePagination'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; + import HistoryIcon from '@mui/icons-material/History'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + import { enqueueSnackbar } from 'notistack'; + import Label from '../../../components/Label'; + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import DownloadIcon from '@mui/icons-material/Download'; + import { LoadingButton } from '@mui/lab'; + -export default function List() { - const { themeStretch } = useSettings(); + export default function DrugList() { const { corporate_id } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const [importResult, setImportResult] = useState(null); - + const navigate = useNavigate(); + function SearchInput(props: any) { - // SEARCH - const searchInput = useRef(null); - const [searchText, setSearchText] = useState(""); + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); - const handleSearchChange = (event: any) => { - const newSearchText = event.target.value ?? '' + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; setSearchText(newSearchText); - } + }; - const handleSearchSubmit = (event: any) => { + const handleSubmit = (event: any) => { event.preventDefault(); props.onSearch(searchText); // Trigger to Parent - } + }; - useEffect(() => { // Trigger First Search + useEffect(() => { setSearchText(searchParams.get('search') ?? ''); - }, [searchParams]) + }, [searchParams]); - return ( -
- + return ( + + - ); - } - - function ImportForm(props: any) { - // IMPORT - // Create Button Menu - const [anchorEl, setAnchorEl] = React.useState(null); - const createMenu = Boolean(anchorEl); - const importForm = useRef(null) - const [currentImportFileName, setCurrentImportFileName] = useState(null) - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleImportButton = () => { - if (importForm?.current) { - handleClose(); - importForm.current ? importForm.current.click() : console.log('No File selected'); - } else { - alert('No file selected') - } - } - - const handleCancelImportButton = () => { - importForm.current.value = ""; - importForm.current.dispatchEvent(new Event("change", { bubbles: true })); - } - - const handleImportChange = (event: any) => { - if (event.target.files[0]) { - setCurrentImportFileName(event.target.files[0].name) - } else { - setCurrentImportFileName(null); - } - } - - const handleUpload = () => { - if (importForm.current?.files.length) { - const formData = new FormData(); - formData.append("file", importForm.current?.files[0]) - axios.post(`corporates/${corporate_id}/import-plan-benefit`, formData ) - .then(response => { - handleCancelImportButton(); - loadDataTableData(); - setImportResult(response.data) - // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); - }) - .catch(response => { - enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) - }) - } else { - enqueueSnackbar('No File Selected', { variant: 'warning' }) - } - } - - return ( -
- - {( !currentImportFileName && - - {/*

kjasndkjandskjasndkjansdkjansd

*/} - - - Import - Download Template - -
- )} - - {( currentImportFileName && - - - - - - - - )} - {( importResult && - - Last Import Result Report : {importResult.result_file?.name ?? "-"} - - )} -
- ); + ); } // Called on every row to map the data to the columns - function createData( icd: Icd ): Icd { - return { - ...icd, - } + function createData(drug: Drug): Drug { + return { + ...drug, + }; } + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importPlan = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importPlan?.current) { + handleClose(); + importPlan.current ? importPlan.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + + const handleCancelImportButton = () => { + if(importPlan.current) + { + importPlan.current.value = ''; + importPlan.current.dispatchEvent(new Event('change', { bubbles: true })); + } + }; + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if(importPlan.current && importPlan.current.files) + { + if (importPlan.current?.files.length) { + const formData = new FormData(); + formData.append('file', importPlan.current?.files[0]); + setImportLoading(true); + axios + .post(`master/drugs/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + console.log(response.data); + setImportLoading(false); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + } + }; + + const handleGetTemplate = () => { + axios.get('master/drugs/download-template').then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }); + }; + + return ( +
+ + {!currentImportFileName && ( + + + + + + Import + + { + handleGetTemplate(); + }} + > + Download Template + + {/* + Download ICD + */} + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed + {importResult.data.failed_rows.map((row, index) => ( + [Code=>{row.code ? row.code : 'Required'},Name=>{row.name ? row.name : 'Required'}] + ))} + + + )} +
+ ); + } // Generate the every row of the table function Row(props: { row: ReturnType }) { - const { row } = props; - const [open, setOpen] = React.useState(false); + const { row } = props; + const [open, setOpen] = React.useState(false); + const style1 = { + color: '#637381' + } - return ( + return ( - *': { borderBottom: 'unset' } }}> - - setOpen(!open)} - > - {open ? : } - + *': open ? {borderBottom: 'unset'} : {}, cursor: open ? 'pointer' : '' }} onClick={() => {if(open==true) setOpen(!open)}}> + {row.type ? row.type : '-'} + {row.code ? row.code : '-'} + {row.name ? row.name : '-'} + {row.version ? row.version : '-'} + + {row.active === 1 ? ( + + ) : ( + + )} + + + + setOpen(!open)}> + + Details + + {/* handleEditData(row)}> + + Edit + */} + handleEditDataStatus(row)}> + + Update Status + + {/* navigate ('/corporates/'+corporate_id+'/hospitals/'+row.id+'/history')}> + + History + */} + + } + /> - {row.type} - {row.code} - {row.name} - {row.version} - - - - - {/* COLLAPSIBLE ROW */} - - - - - - Description : {row.description} - - - + + {/* COLLAPSIBLE ROW */} + {if(open==true) setOpen(!open)}}> + + + + + Detail + + + Code: + {row.code ? row.code : '-'} + + + Name: + {row.name ? row.name : '-'} + + + + + - + - ); + ); } // Dummy Default Data - const [dataTableIsLoading, setDataTableLoading] = useState(true); - const [dataTableLastRequest, setDataTableLastRequest] = useState(0); - const [dataTableResponseState, setDataTableResponseState] = useState('idle'); - const [dataTableData, setDataTableData] = useState({ - current_page: 1, - data: [], - path: "", - first_page_url: "", - last_page: 1, - last_page_url: "", - next_page_url: "", - prev_page_url: "", - per_page: 10, - from: 0, - to: 0, - total: 0 + const [dataTableIsLoading, setDataTableLoading] = React.useState(true); + const [dataTableData, setDataTableData] = React.useState({ + current_page: 1, + data: [], + path: '', + first_page_url: '', + last_page: 1, + last_page_url: '', + next_page_url: '', + prev_page_url: '', + per_page: 10, + from: 0, + to: 0, + total: 0, }); - const [dataTablePage, setDataTablePage] = useState(5); - const loadDataTableData = async (appliedFilter : any | null = null) => { - setDataTableLoading(true); - const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); - const response = await axios.get('/master/drugs', { params: filter }); - // console.log(response.data); - setDataTableLoading(false); - - setDataTableData(response.data); - } - - const headStyle = { - fontWeight: 'bold', + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + // Get Data Drugs + const response = await axios.get('/master/drugs', { params: filter }); + setDataTableLoading(false); + setDataTableData(response.data); }; - const applyFilter = async (searchFilter: string) => { - await loadDataTableData({ "search" : searchFilter }); - setSearchParams({ "search" : searchFilter }); - } + const applyFilter = async (searchFilter: any) => { + await loadDataTableData({ search: searchFilter }); + setSearchParams({ search: searchFilter }); + }; - const handlePageChange = (event : ChangeEvent, value: number) => { - const filter = Object.fromEntries([...searchParams.entries(), ["page", value]]); - loadDataTableData(filter); - setSearchParams(filter); - } + const handlePageChange = (event: ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; useEffect(() => { - loadDataTableData(); - }, []) - - return ( - - + loadDataTableData(); + }, []); - - {/* The Main Table */} - -
- - - - Type - Code - Name - Version - Status - Action - - - {dataTableIsLoading ? - ( - + //validation dialog + const [nameField, setNameField] = useState(''); + const [codeField, setCodeField] = useState(''); + // ID for edit data + const [idField, setIdField] = useState(''); + + const handleAddData = () => { + navigate('/corporates/'+corporate_id+'/hospitals/create'); + } + //Edit data + const handleEditData = (data : any) => { + navigate('/corporates/'+corporate_id+'/hospitals/edit/'+data.id+'/'+data.organization_id); + } + // End dialog for hospitals + + // Dialog for update status hospitals + const [openDialogStatus, setOpenDialogStatus] = useState(false); + const [activeField, setActiveField] = useState(0); + const [reasonUpdate,setReasonUpdate] = useState('Agreement changed'); + + const handleCloseDialogUpdate = () => { + setOpenDialogStatus(false); + setNameField(''); + setCodeField(''); + setIdField(''); + setActiveField(activeField); + } + + const handleSaveUpdateData = () => { + let activeValue = 0; + if(activeField === 1) + { + activeValue = 0; + } + else + { + activeValue = 1; + } + const updateData = { + reason: reasonUpdate, + active : activeValue, + id: idField, + }; + axios + .put('/master/drugs/'+idField+'/activation', updateData) + .then((response) => { + enqueueSnackbar('Data updated successfully', { variant: 'success' }); + loadDataTableData(); + handleCloseDialogUpdate(); + }) + .catch((error) => { + enqueueSnackbar('Failed to add data', { variant: 'error' }); + }); + } + + const handleEditDataStatus = (data: any) => { + setIdField(data.id); + setNameField(data.name); + setCodeField(data.code); + setActiveField(data.active); + setOpenDialogStatus(true); + } + // End dialog for update status devisions + + + + return ( + + + + {/* The Main Table */} + +
+ {/* Table Head */} + - Loading + + Type + + + Code + + + Name + + + Version + + + Status + + + - - ) : ( - dataTableData.data.length == 0 ? - ( + + {/* Condition Table Body */} + {dataTableIsLoading ? ( - - No Data - + + + Loading + + - ) : ( + ) : dataTableData.data.length == 0 ? ( - {dataTableData.data.map(row => ( + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( - ))} + ))} - ) - )} -
- - - - - + )} + + + {/* Paginations */} + + + {/* Dialog Update Status */} + + + + + Update Status + + + + + + + + + Are you sure to {activeField == 1 ? 'Inactive' : 'Active'} this drug ? + + + Code + {nameField} + + + Name + {codeField} + + + + + Reason for update* + + + Reason for update + + + + + + + + + + + + + ); -} + } \ No newline at end of file diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index de0d3b64..75a4c793 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -377,10 +377,18 @@ export default function Router() { path: 'claim-requests', element: , }, + { + path: 'claim-requests/edit/:id', + element: , + }, { path: 'claims/create', element: , }, + { + path: 'claims/edit/:id', + element: , + }, { path: 'claims/:id', element: , @@ -550,6 +558,7 @@ const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate')) const ClaimShow = Loadable(lazy(() => import('../pages/Claims/Show'))); const ClaimRequests = Loadable(lazy(() => import('../pages/ClaimRequests/Index'))); +const ClaimRequestsCreate = Loadable(lazy(() => import('../pages/ClaimRequests/CreateUpdate'))); const Membership = Loadable(lazy(() => import('../pages/Service/Membership/index'))); diff --git a/frontend/dashboard/src/utils/formatString.ts b/frontend/dashboard/src/utils/formatString.ts index 5589c2f4..1346b2e9 100644 --- a/frontend/dashboard/src/utils/formatString.ts +++ b/frontend/dashboard/src/utils/formatString.ts @@ -1,3 +1,5 @@ +import { string } from "yup"; + export function clearTag(htmlString: string | undefined) { return htmlString?.replace(/(<([^>]+)>)/gi, ""); } @@ -9,3 +11,9 @@ export function limitString(anyString: string | undefined, limit: number = 50) { export function makeExcerpt(htmlString: string | undefined, limit: number = 50) { return limitString(clearTag(htmlString ?? ''), limit); } + +export function capitalizeFirstLetter(text: string) { + return text.charAt(0).toUpperCase() + text.slice(1); +} + + diff --git a/frontend/dashboard/src/utils/formatTime.ts b/frontend/dashboard/src/utils/formatTime.ts index c2df7cc7..800fe08d 100644 --- a/frontend/dashboard/src/utils/formatTime.ts +++ b/frontend/dashboard/src/utils/formatTime.ts @@ -10,6 +10,10 @@ export function fDateTime(date: Date | string | number) { return format(new Date(date), 'dd MMM yyyy p'); } +export function fDateTimesecond(date: Date | string | number) { + return format(new Date(date), 'dd MMM yyyy HH:mm:ss'); +} + export function fTimestamp(date: Date | string | number) { return getTime(new Date(date)); } diff --git a/public/files/Template - Drugs.xlsx b/public/files/Template - Drugs.xlsx new file mode 100644 index 00000000..8e320c7f Binary files /dev/null and b/public/files/Template - Drugs.xlsx differ diff --git a/public/files/Template Format Claim.xlsx b/public/files/Template Format Claim.xlsx new file mode 100644 index 00000000..3705a159 Binary files /dev/null and b/public/files/Template Format Claim.xlsx differ diff --git a/public/files/TemplateFormulariumList.xlsx b/public/files/TemplateFormulariumList.xlsx index 348dafe3..65cec25b 100644 Binary files a/public/files/TemplateFormulariumList.xlsx and b/public/files/TemplateFormulariumList.xlsx differ