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/Routes/api.php b/Modules/Internal/Routes/api.php index 3485ed3a..8d80cf63 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -213,6 +213,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/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..4e15a358 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,105 @@ 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 { + + } 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_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/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/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 76ec6275..21bdf19c 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -369,10 +369,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: , @@ -540,6 +548,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 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