diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php new file mode 100644 index 00000000..ce98c742 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -0,0 +1,124 @@ +latest() + ->paginate(10); + + return response()->json($claims); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + $request->validate([ + 'diagnosis_id' => 'required', + 'member_id' => 'required', + 'total_claim' => 'required', + 'benefit_id' => 'required' + ]); + + // return response()->json($request->toArray()); + + $member = Member::find($request->member_id); + $benefit = Benefit::find($request->benefit_id); + $diagnosis = Icd::find($request->diagnosis_id); + + // Check Eligibility + $validation = ClaimService::checkMemberEligibility($member, $benefit, $diagnosis, $request->total_claim); + + // Store Claim + if ($validation['isEligible']) { + $claim = ClaimService::storeClaim($member, $diagnosis, $request->total_claim, $benefit); + } else { + return response()->json([ + 'data' => $validation, + 'message' => $validation['errors'][0]['message'] + ], 403); + } + + return response()->json($claim); + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('internal::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('internal::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + public function checkLimit(Request $request) + { + return true; + } +} diff --git a/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php b/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php new file mode 100755 index 00000000..c44197ba --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php @@ -0,0 +1,205 @@ +memberEnrollmentService = $memberEnrollmentService; + } + /** + * Display a listing of the resource. + * @return Renderable + */ + public function index(Request $request, $corporate_id) + { + $members = Member::query() + ->filter($request->all()) + // ->where('corporate_id', $corporate_id) + ->whereHas('employeds', function ($employeds) use ($corporate_id) { + $employeds->where('corporate_id', $corporate_id); + }) + ->with([ + 'employeds', + 'currentPolicy', + 'claims' => function ($claim) { + return $claim->used(); + } + ]) + ->with('currentPlan') + ->paginate() + ->appends($request->all()); + + return $members; + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('internal::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('internal::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + + public function import(Request $request, $corporate_id) + { + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + $corporate = Corporate::findOrFail($corporate_id)->load('currentPolicy'); + + $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + + $reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name)); + $reader->open(Storage::path('temp/'.$file_name)); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile(Storage::disk('public')->path('temp/result-'.$file_name)); + + $headers_map_to_table_fields = $this->memberEnrollmentService->doc_headers_to_field_map; + + // Write Header to File with certain Format from MemberEnrollmentService::$result_doc_headers + $result_headers = $this->memberEnrollmentService->result_doc_headers; + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($result_headers)); + $writer->addRow($singleRow); + + $imported_member_data = 0; + $failed_member_data = []; + foreach ($reader->getSheetIterator() as $sheet) { + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + // Clear up the string and remove all spaces + $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; + } + } else { // Next Row Should be Data + // Collecting Values from table rows and map it to correct fields + $new_member_data = []; + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) { + $new_member_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); + } + } + + try { + // dd($new_member_data); + $rowResponse = $this->memberEnrollmentService->handleImportRow($corporate, $new_member_data); + + // Write Success Result to File + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($rowResponse)); + $writer->addRow($singleRow); + $imported_member_data++; + } catch (ImportRowException $e) { + // Write Data Validation Error to File + $new_member_data = array_merge($new_member_data, [ + 'ingestion_code' => $e->getCode(), + 'ingestion_status' => $e->getMessage(), + ]); + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); + $writer->addRow($singleRow); + $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; + } catch (\Exception $e) { + // Write Server Error to File + $new_member_data = array_merge($new_member_data, [ + 'ingestion_code' => $e->getCode(), + 'ingestion_status' => $e->getMessage(), + ]); + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); + $writer->addRow($singleRow); + $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; + } + } + + } + + break; //only read first sheet + } + $reader->close(); + $writer->close(); + Storage::delete('temp/'.$file_name); + // throw(404); + + return [ + 'total_success_row' => $imported_member_data, + 'total_failed_row' => count($failed_member_data), + 'failed_row' => $failed_member_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-'.$file_name), + 'name' => 'result-'.$file_name, + ] + ]; + } +} diff --git a/Modules/Internal/Http/Controllers/Api/DiagnosisController.php b/Modules/Internal/Http/Controllers/Api/DiagnosisController.php index 1d49b710..f42a6b49 100755 --- a/Modules/Internal/Http/Controllers/Api/DiagnosisController.php +++ b/Modules/Internal/Http/Controllers/Api/DiagnosisController.php @@ -79,4 +79,12 @@ class DiagnosisController extends Controller { // } + + public function search(Request $request) + { + return Icd::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('code', 'LIKE', '%'.$search.'%'); + })->limit(10)->get(); + } } diff --git a/Modules/Internal/Http/Controllers/Api/MemberController.php b/Modules/Internal/Http/Controllers/Api/MemberController.php old mode 100755 new mode 100644 index fe178a21..96925c7b --- a/Modules/Internal/Http/Controllers/Api/MemberController.php +++ b/Modules/Internal/Http/Controllers/Api/MemberController.php @@ -2,48 +2,22 @@ namespace Modules\Internal\Http\Controllers\Api; -use App\Exceptions\ImportRowException; -use App\Models\Corporate; use App\Models\Member; -use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; -use Box\Spout\Writer\Common\Creator\WriterEntityFactory; -use Box\Spout\Common\Entity\Row; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Illuminate\Support\Facades\Storage; -use Modules\Internal\Services\MemberEnrollmentService; class MemberController extends Controller { - public function __construct(MemberEnrollmentService $memberEnrollmentService) - { - $this->memberEnrollmentService = $memberEnrollmentService; - } /** * Display a listing of the resource. * @return Renderable */ - public function index(Request $request, $corporate_id) + public function index() { - $benefits = Member::query() - ->filter($request->all()) - // ->where('corporate_id', $corporate_id) - ->whereHas('employeds', function ($employeds) use ($corporate_id) { - $employeds->where('corporate_id', $corporate_id); - }) - ->with([ - 'employeds', - 'currentPolicy' => function ($policy) use ($corporate_id) { - $policy->whereHas('corporatePolicy', function($corporatePolicy) use ($corporate_id) { - $corporatePolicy->where('corporate_id', $corporate_id); - }); - } - ]) - ->paginate() - ->appends($request->all()); - - return $benefits; + return Member::query() + ->with('currentPlan') + ->paginate(); } /** @@ -106,100 +80,10 @@ class MemberController extends Controller // } - - public function import(Request $request, $corporate_id) + public function benefits($member_id) { - $request->validate([ - 'file' => 'required|file|mimes:xls,xlsx,csv,txt', - ]); - $corporate = Corporate::findOrFail($corporate_id)->load('currentPolicy'); - - $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); - $file = $request->file('file')->storeAs('temp', $file_name); + $member = Member::findOrFail($member_id); - $reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name)); - $reader->open(Storage::path('temp/'.$file_name)); - - $writer = WriterEntityFactory::createXLSXWriter(); - $writer->openToFile(Storage::disk('public')->path('temp/result-'.$file_name)); - - $headers_map_to_table_fields = $this->memberEnrollmentService->doc_headers_to_field_map; - - // Write Header to File with certain Format from MemberEnrollmentService::$result_doc_headers - $result_headers = $this->memberEnrollmentService->result_doc_headers; - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($result_headers)); - $writer->addRow($singleRow); - - $imported_member_data = 0; - $failed_member_data = []; - foreach ($reader->getSheetIterator() as $sheet) { - $doc_headers_indexes = []; - foreach ($sheet->getRowIterator() as $index => $row) { - if ($index == 1) { // First Row Must be Header - foreach ($row->getCells() as $index => $cell) { - // Clear up the string and remove all spaces - $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; - } - } else { // Next Row Should be Data - // Collecting Values from table rows and map it to correct fields - $new_member_data = []; - foreach ($row->getCells() as $header_index => $cell) { - if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) { - $new_member_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); - } - } - - try { - // dd($new_member_data); - $rowResponse = $this->memberEnrollmentService->handleImportRow($corporate, $new_member_data); - - // Write Success Result to File - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($rowResponse)); - $writer->addRow($singleRow); - $imported_member_data++; - } catch (ImportRowException $e) { - // Write Data Validation Error to File - $new_member_data = array_merge($new_member_data, [ - 'ingestion_code' => $e->getCode(), - 'ingestion_status' => $e->getMessage(), - ]); - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); - $writer->addRow($singleRow); - $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; - } catch (\Exception $e) { - // Write Server Error to File - $new_member_data = array_merge($new_member_data, [ - 'ingestion_code' => $e->getCode(), - 'ingestion_status' => $e->getMessage(), - ]); - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); - $writer->addRow($singleRow); - $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; - } - } - - } - - break; //only read first sheet - } - $reader->close(); - $writer->close(); - Storage::delete('temp/'.$file_name); - // throw(404); - - return [ - 'total_success_row' => $imported_member_data, - 'total_failed_row' => count($failed_member_data), - 'failed_row' => $failed_member_data, - 'result_file' => [ - 'url' => Storage::disk('public')->url('temp/result-'.$file_name), - 'name' => 'result-'.$file_name, - ] - ]; + return response()->json($member->currentPlan->benefits()->select(['description', 'code', 'id'])->get()); } } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 6d3c1b46..c9eab7ed 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -1,11 +1,14 @@ group(function () { Route::get('corporates/{corporate_id}/divisions/{id}/edit', [DivisionController::class, 'edit']); Route::put('corporates/{corporate_id}/divisions/{id}', [DivisionController::class, 'update']); - Route::get('corporates/{corporate_id}/members', [MemberController::class, 'index']); - Route::post('corporates/{corporate_id}/members/import', [MemberController::class, 'import']); + Route::get('corporates/{corporate_id}/members', [CorporateMemberController::class, 'index']); + Route::post('corporates/{corporate_id}/members/import', [CorporateMemberController::class, 'import']); Route::get('corporates/{corporate_id}/diagnosis-exclusions', [DiagnosisExclusionController::class, 'index']); Route::post('corporates/{corporate_id}/diagnosis-exclusions/import', [DiagnosisExclusionController::class, 'import']); @@ -86,12 +89,19 @@ Route::prefix('internal')->group(function () { // Route::get('corporates/{corporate_id}/diagnosis-exclusions/import', [DiagnosisExclusionController::class, 'import']); Route::get('master/diagnosis', [DiagnosisController::class, 'index']); + Route::get('master/diagnosis/search', [DiagnosisController::class, 'search']); Route::get('master/drugs', [DrugController::class, 'index']); Route::get('master/formulariums', [FormulariumController::class, 'index']); Route::post('master/formulariums', [FormulariumController::class, 'store']); Route::post('master/formulariums/import', [FormulariumController::class, 'import']); - + Route::get('members', [MemberController::class, 'index']); + Route::get('members/{member_id}/benefits', [MemberController::class, 'benefits']); + + Route::get('claims', [ClaimController::class, 'index']); + Route::post('claims', [ClaimController::class, 'store']); + Route::post('check-limit', [ClaimController::class, 'checkLimit']); + }); // Route::get('something', [DiagnosisExclusionController::class, 'index']); diff --git a/Modules/Internal/Services/ClaimService.php b/Modules/Internal/Services/ClaimService.php new file mode 100644 index 00000000..569cf8dd --- /dev/null +++ b/Modules/Internal/Services/ClaimService.php @@ -0,0 +1,115 @@ +currentPlan; + $policy = $member->currentPolicy; + $corporate = $policy->corporate; + + + $isEligible = true; + $validationErrors = []; + + // Eligibility Validation + + if (!in_array($member->marital_status, explode(',', $benefit->msc))) { + $validationErrors[] = [ + 'error' => 'msc', + 'message' => 'Only '.$benefit->msc + ]; + $isEligible = false; + } + + if (!in_array($member->gender_code, explode(',', $benefit->genders))) { + $validationErrors[] = [ + 'error' => 'genders', + 'message' => 'Only '.$benefit->genders + ]; + $isEligible = false; + } + + if (!empty($benefit->min_age) && $member->age < $benefit->min_age) { + $validationErrors[] = [ + 'error' => 'min_age', + 'message' => 'Minimum Age is '.$benefit->min_age + ]; + $isEligible = false; + } + + if (!empty($benefit->max_age) && $member->age > $benefit->max_age) { + $validationErrors[] = [ + 'error' => 'max_age', + 'message' => 'Maximum Age is '.$benefit->min_age + ]; + $isEligible = false; + } + + // TODO complete validations + + // Limit Validation + if ($totalClaim > 0) { + if (bcsub($corporate->limit_balance, $totalClaim) < 0) { + $validationErrors[] = [ + 'error' => 'corporate_limit', + 'message' => 'Corporate Limit cannot cover this' + ]; + $isEligible = false; + } + + if (bcsub($benefit->limit_amount, $totalClaim) < 0) { + $validationErrors[] = [ + 'error' => 'benefit_limit', + 'message' => 'Benefit Limit cannot cover this' + ]; + $isEligible = false; + } + + // TODO complete validations + + } + + return [ + 'isEligible' => $isEligible, + 'errors' => $validationErrors + ]; + } + + public static function storeClaim($member, $diagnosis, $totalClaim, $benefit) + { + try { + DB::beginTransaction(); + + $claim = Claim::create([ + 'member_id' => $member->id, + 'diagnosis_id' => $diagnosis->id, + 'total_claim' => $totalClaim, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'benefit_id' => $benefit->id, + ]); + + $policy = $member->currentPolicy; + $policy->limitJournals()->create([ + 'previous_balance' => $policy->limit_balance, + 'total_credit' => $totalClaim, + 'type' => 'credit', + 'balance' => bcsub($policy->limit_balance, $totalClaim), + 'description' => 'Log for Claim #'. $claim->code, + ]); + + DB::commit(); + return $claim; + } catch (\Exception $error) { + DB::rollBack(); + + return false; + } + } +} \ No newline at end of file diff --git a/Modules/Internal/Services/MemberEnrollmentService.php b/Modules/Internal/Services/MemberEnrollmentService.php index 011ea50f..50a7c283 100755 --- a/Modules/Internal/Services/MemberEnrollmentService.php +++ b/Modules/Internal/Services/MemberEnrollmentService.php @@ -423,6 +423,16 @@ class MemberEnrollmentService ]), 0, null, $row); } + // Validate If Plan Exist + // TODO validate corporate plan + $plan = Plan::query() + ->where('code', $row['plan_id']) + ->where('corporate_id', $corporate->id) + ->first(); + if (!$plan) { + throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); + } + $this->validateRow($row); try { @@ -450,6 +460,13 @@ class MemberEnrollmentService 'nik' => $row['nik'], 'status' => $row['employment_status'] ]); + + $member->memberPlans()->create([ + 'plan_id' => $plan->id, + 'status' => 'active', + 'start' => Carbon::parse(strtotime($row['member_effective_date'])), + 'end' => Carbon::parse(strtotime($row['member_expiry_date'])), + ]); } DB::commit(); } catch (\Exception $e) { @@ -458,35 +475,65 @@ class MemberEnrollmentService } break; case "2": // Member Information Update (Without Replacement Card) - $memberPolicy = MemberPolicy::query() - ->where('policy_id', $row['policy_number']) - ->where('member_id', $row['member_id']) - ->with('member') - ->first(); + $member = Member::query() + ->where('member_id', $row['member_id']) + ->first(); + + // Validate If Exist Member + if (!$member) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_FOUND', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } - if (!$memberPolicy) { - throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); + try { + $memberPolicy = MemberPolicy::query() + ->where('policy_id', $row['policy_number']) + ->where('member_id', $row['member_id']) + ->with('member') + ->first(); + + if (!$memberPolicy) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + if ($memberPolicy->status != 'active') { + throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + $memberPolicy->member->fill($member_data); + if (!$memberPolicy->member->isDirty()) { + throw new ImportRowException(__('enrollment.MEMBER_NO_CHANGE'), 0, null, $row); + } + + $memberPolicy->member->save(); + DB::commit(); + } catch (\Exception $e) { + DB::rollback(); + throw new ImportRowException($e->getMessage(), $e->getCode(), $e, $row); } - - if ($memberPolicy->status != 'active') { - throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); - } - - $memberPolicy->member->fill($member_data); - if (!$memberPolicy->member->isDirty()) { - throw new ImportRowException(__('enrollment.MEMBER_NO_CHANGE'), 0, null, $row); - } - - $memberPolicy->member->save(); break; case "3": // Member Deletion + $member = Member::query() + ->where('member_id', $row['member_id']) + ->first(); + + // Validate If Exist Member + if (!$member) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_FOUND', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + $memberPolicy = MemberPolicy::query() ->where('policy_id', $row['policy_number']) ->where('member_id', $row['member_id']) diff --git a/app/Http/Controllers/Api/MemberController.php b/app/Http/Controllers/Api/MemberController.php index 164aa93b..97bcfd29 100755 --- a/app/Http/Controllers/Api/MemberController.php +++ b/app/Http/Controllers/Api/MemberController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Models\Member; use Illuminate\Http\Request; class MemberController extends Controller diff --git a/app/Models/Claim.php b/app/Models/Claim.php new file mode 100644 index 00000000..683ae02b --- /dev/null +++ b/app/Models/Claim.php @@ -0,0 +1,81 @@ + 'Draft', + 'requested' => 'Requested', + 'received' => 'Received', + 'approved' => 'Approved', + 'paid' => 'Paid', + 'declined' => 'Declined' + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($model) { + try { + $model->code = (string) Str::orderedUuid(); // generate uuid + } catch (\Exception $e) { + abort(500, $e->getMessage()); + } + }); + } + + public function member() + { + return $this->belongsTo(Member::class, 'member_id'); + } + + public function diagnosis() + { + return $this->belongsTo(Icd::class, 'diagnosis_id'); + } + + public function plan() + { + return $this->belongsTo(Plan::class, 'plan_id'); + } + + public function benefit() + { + return $this->belongsTo(Benefit::class, 'benefit_id'); + } + + public function scopeUsed($query, $startDate, $endDate) + { + return $query + ->whereIn('status', ['approved', 'paid']) + ->whereBetween('requested_at', $startDate, $endDate); + } + +} diff --git a/app/Models/Corporate.php b/app/Models/Corporate.php index 561af4a6..04ad740f 100755 --- a/app/Models/Corporate.php +++ b/app/Models/Corporate.php @@ -82,6 +82,11 @@ class Corporate extends Model return $this->morphMany(ImportLog::class, 'importable'); } + public function limitJournals() + { + return $this->morphMany(LimitJournal::class, 'journalable'); + } + public function services() { return $this->hasManyThrough(CorporateService::class, Service::class, 'corporate_id', 'service_code', 'id', 'service_code'); @@ -101,4 +106,9 @@ class Corporate extends Model { return $this->hasMany(Corporate::class, 'parent_id'); } + + public function getLimitBalanceAttribute() + { + return $this->currentPolicy->limit_balance; + } } diff --git a/app/Models/CorporateManager.php b/app/Models/CorporateManager.php new file mode 100644 index 00000000..bc3aa8a0 --- /dev/null +++ b/app/Models/CorporateManager.php @@ -0,0 +1,13 @@ +belongsTo(Corporate::class); } + public function limitJournals() + { + return $this->morphMany(LimitJournal::class, 'journalable'); + } + + public function latestLimitJournal() + { + return $this->morphOne(LimitJournal::class, 'journalable')->latestOfMany(); + } + public function setCodeAttribute($value) { $this->attributes['code'] = !empty($value) ? $value : Str::upper(Str::random('6')); @@ -53,4 +71,11 @@ class CorporatePolicy extends Model { $this->attributes['end'] = !empty($value) ? Carbon::parse($value)->format('Y-m-d') : null; } + + public function getLimitBalanceAttribute() + { + $journal = $this->latestLimitJournal; + + return $journal ? $journal->balance : (!empty($this->total_premi) ? $this->total_premi : "0"); + } } diff --git a/app/Models/Icd.php b/app/Models/Icd.php index 40115736..9dfa57ff 100755 --- a/app/Models/Icd.php +++ b/app/Models/Icd.php @@ -27,6 +27,15 @@ class Icd extends Model 'active' ]; + protected $hidden = [ + 'created_at', + 'updated_at', + 'deleted_at', + 'created_by', + 'updated_by', + 'deleted_by', + ]; + public function getTypeAttribute() { return 'ICD-'.$this->rev; diff --git a/app/Models/LimitJournal.php b/app/Models/LimitJournal.php new file mode 100644 index 00000000..707715e9 --- /dev/null +++ b/app/Models/LimitJournal.php @@ -0,0 +1,37 @@ +morphTo(); + } + + public function setTotalCreditAttribute($value) + { + $this->attributes['total_credit'] = empty($value) ? 0 : $value; + } + + public function setTotalDebitAttribute($value) + { + $this->attributes['total_debit'] = empty($value) ? 0 : $value; + } + +} diff --git a/app/Models/Member.php b/app/Models/Member.php index ea1657d7..78f4a323 100755 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Traits\Blameable; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -56,11 +57,61 @@ class Member extends Model "end_no_claim", ]; + protected $appends = [ + 'full_name', + 'age', + 'gender_code', + '' + ]; + + protected $hidden = [ + 'created_at', + 'updated_at', + 'deleted_at', + 'created_by', + 'updated_by', + 'deleted_by', + ]; + + public function claims() + { + return $this->hasMany(Claim::class, 'member_id', 'id'); + } + public function employeds() { return $this->hasMany(CorporateEmployee::class, 'member_id'); } + public function corporates() + { + return $this + ->belongsToMany(Corporate::class, 'corporate_employees', 'corporate_id', 'member_id') + ->withPivot([ + 'branch_code', + 'divison_id', + 'nik', + 'status', + 'start', + 'end' + ]); + } + + public function currentCorporate() + { + return $this->belongsToMany(Corporate::class, 'corporate_employees', 'corporate_id', 'member_id') + // ->withPivot([ + // 'branch_code', + // 'divison_id', + // 'nik', + // 'status', + // 'start', + // 'end' + // ]) + ->where('start', '<', now()) + ->where('end', '>', now()); + } + public function memberPlans() { return $this->hasMany(MemberPlan::class, 'member_id'); @@ -68,9 +119,14 @@ class Member extends Model public function plans() { - return $this->hasManyThrough(MemberPlan::class, Plan::class, 'member_id', 'plan_id', 'id', 'plan_id'); + return $this->hasManyThrough(Plan::class, MemberPlan::class, 'member_id', 'id', 'id', 'plan_id'); } + public function currentPlan() + { + return $this->hasOneThrough(Plan::class, MemberPlan::class, 'member_id', 'id', 'id', 'plan_id')->latest(); + } + public function policies() { return $this->hasMany(MemberPolicy::class, 'member_id', 'member_id'); @@ -78,7 +134,40 @@ class Member extends Model public function currentPolicy() { - return $this->hasOne(MemberPolicy::class, 'member_id', 'member_id')->where('status', 'active')->latestOfMany(); + return $this->hasOneThrough(CorporatePolicy::class, MemberPolicy::class, 'member_id', 'code', 'member_id', 'policy_id') + ->where('corporate_policies.start', '<', now()) + ->where('corporate_policies.end', '>', now()) + ->where('member_policies.start', '<', now()) + ->where('member_policies.end', '>', now()); + // return $this->hasOne(MemberPolicy::class, 'member_id', 'member_id')->where('status', 'active')->latestOfMany(); + } + + public function getAgeAttribute() + { + if ($this->birth_date) { + return Carbon::parse($this->birth_date)->diffInYears(now()); + } else { + return null; + } + } + + public function getFullNameAttribute() + { + $arr = []; + if (!empty($this->name_prefix)) { + $arr[] = $this->name_prefix; + } + $arr[] = $this->name; + if (!empty($this->name_suffix)) { + $arr[] = $this->name_suffix; + } + + return implode(' ', $arr); + } + + public function getGenderCodeAttribute() + { + return $this->gender ? ($this->gender == 'female' ? 'F' : 'M') : $this->gender; } public function scopeFilter($query, array $filters) diff --git a/app/Models/Plan.php b/app/Models/Plan.php index 28943183..b486291b 100755 --- a/app/Models/Plan.php +++ b/app/Models/Plan.php @@ -165,4 +165,9 @@ class Plan extends Model // { // return $this->belongsTo(Corporate::class); // } + + public function benefits() + { + return $this->hasMany(Benefit::class, 'plan_code', 'id'); + } } diff --git a/app/Services/ClaimService.php b/app/Services/ClaimService.php new file mode 100644 index 00000000..050644b7 --- /dev/null +++ b/app/Services/ClaimService.php @@ -0,0 +1,44 @@ + Str::random('16'), + 'member_id' => $member->id, + 'diagnosis_id' => $icd, + 'total_claim' => $totalClaim, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'benefit_id' => $benefit->id, + ]); + + $corporate = $member->asd; + + return $claim; + } + + public static function getMemberTotalUsage(Member $member, $startDate = null, $endDate = null) + { + $startDate = empty($startDate) ? Carbon::now()->startOfMonth() : $startDate; + $endDate = empty($endDate) ? Carbon::now()->startOfMonth() : $endDate; + + $claims = $member->claims() + ->used($startDate, $endDate) + ->get(); + $total = $claims->sum(function($claim){ + return $claim->total_claim; + }); + + return $total; + } +} \ No newline at end of file diff --git a/database/migrations/2022_07_07_040543_create_corporate_plans_table.php b/database/migrations/2022_07_07_040543_create_corporate_plans_table.php index 7042d877..f0218877 100755 --- a/database/migrations/2022_07_07_040543_create_corporate_plans_table.php +++ b/database/migrations/2022_07_07_040543_create_corporate_plans_table.php @@ -13,22 +13,22 @@ return new class extends Migration */ public function up() { - Schema::create('corporate_plans', function (Blueprint $table) { - $table->id(); - $table->foreignId('corporate_id')->nullable()->index(); - $table->string('code')->index(); - $table->string('name')->nullable(); - $table->text('description')->nullable(); - $table->boolean('active')->default(true); - $table->timestamps(); - $table->softDeletes(); + // Schema::create('corporate_plans', function (Blueprint $table) { + // $table->id(); + // $table->foreignId('corporate_id')->nullable()->index(); + // $table->string('code')->index(); + // $table->string('name')->nullable(); + // $table->text('description')->nullable(); + // $table->boolean('active')->default(true); + // $table->timestamps(); + // $table->softDeletes(); - $table->foreignId('created_by')->nullable()->index(); - $table->foreignId('updated_by')->nullable()->index(); - $table->foreignId('deleted_by')->nullable()->index(); + // $table->foreignId('created_by')->nullable()->index(); + // $table->foreignId('updated_by')->nullable()->index(); + // $table->foreignId('deleted_by')->nullable()->index(); - $table->unique(["corporate_id", "code"], 'corporate_plans_unique'); - }); + // $table->unique(["corporate_id", "code"], 'corporate_plans_unique'); + // }); } /** @@ -38,6 +38,6 @@ return new class extends Migration */ public function down() { - Schema::dropIfExists('corporate_plans'); + // Schema::dropIfExists('corporate_plans'); } }; diff --git a/database/migrations/2022_11_22_135948_create_claims_table.php b/database/migrations/2022_11_22_135948_create_claims_table.php new file mode 100644 index 00000000..6416783c --- /dev/null +++ b/database/migrations/2022_11_22_135948_create_claims_table.php @@ -0,0 +1,58 @@ +id(); + $table->string('code')->index(); + $table->foreignId('member_id')->index(); + $table->foreignId('diagnosis_id')->index(); + $table->string('total_claim'); + $table->string('currency'); + $table->foreignId('plan_id')->index(); + $table->foreignId('benefit_id')->index(); + + $table->dateTime('requested_at')->nullable(); + $table->unsignedBigInteger('requested_by')->nullable()->index(); + + $table->dateTime('received_at')->nullable(); + $table->unsignedBigInteger('received_by')->nullable()->index(); + + $table->dateTime('approved_at')->nullable(); + $table->unsignedBigInteger('approved_by')->nullable()->index(); + + $table->dateTime('approved_at')->nullable(); + $table->unsignedBigInteger('approved_by')->nullable()->index(); + + $table->dateTime('paid_at')->nullable(); + $table->unsignedBigInteger('paid_by')->nullable()->index(); + + $table->timestamps(); + $table->softDeletes(); + $table->unsignedBigInteger('created_by')->nullable()->index(); + $table->unsignedBigInteger('updated_by')->nullable()->index(); + $table->unsignedBigInteger('deleted_by')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('claims'); + } +}; diff --git a/database/migrations/2022_11_23_140658_create_limit_journals_table.php b/database/migrations/2022_11_23_140658_create_limit_journals_table.php new file mode 100644 index 00000000..3682ca24 --- /dev/null +++ b/database/migrations/2022_11_23_140658_create_limit_journals_table.php @@ -0,0 +1,43 @@ +id(); + $table->morphs('journalable'); + $table->string('previous_balance'); + $table->string('total_debit')->nullable()->comment('Should be in positive value'); + $table->string('total_credit')->nullable()->comment('Should be in negative value'); + $table->string('type'); + $table->string('balance'); + $table->text('description'); + + $table->timestamps(); + $table->softDeletes(); + $table->unsignedBigInteger('created_by')->nullable()->index(); + $table->unsignedBigInteger('updated_by')->nullable()->index(); + $table->unsignedBigInteger('deleted_by')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('limit_journals'); + } +}; diff --git a/database/seeders/DummyClaimSeeder.php b/database/seeders/DummyClaimSeeder.php new file mode 100644 index 00000000..3ea7a395 --- /dev/null +++ b/database/seeders/DummyClaimSeeder.php @@ -0,0 +1,36 @@ +get(); + + foreach ($members as $member) { + for ($x = 0; $x < 10; $x++) { + $member->claims()->create([ + 'code' => Str::random('16'), + 'member_id' => $member->id, + 'diagnosis_id' => Icd::inRandomOrder()->first()->id, + 'total_claim' => 5000000, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'benefit_id' => $member->currentPlan->benefits()->inRandomOrder()->first()->id, + ]); + } + } + } +} diff --git a/database/seeders/DummyMemberSeeder.php b/database/seeders/DummyMemberSeeder.php index 361510f4..bb0ce713 100755 --- a/database/seeders/DummyMemberSeeder.php +++ b/database/seeders/DummyMemberSeeder.php @@ -16,9 +16,19 @@ class DummyMemberSeeder extends Seeder */ public function run() { - User::create([ - 'email' => 'admin@linksehat.dev', - 'password' => Hash::make('password') - ]); + $userEmails = [ + 'admin@linksehat.dev', + 'manager+one@gmail.com', + 'manager+two@gmail.com' + ]; + + foreach ($userEmails as $email) { + User::updateOrCreate([ + 'email' => $email + ], [ + 'email' => $email, + 'password' => Hash::make('password') + ]); + } } } diff --git a/frontend/client-portal/package.json b/frontend/client-portal/package.json index 0ee98044..15207f05 100755 --- a/frontend/client-portal/package.json +++ b/frontend/client-portal/package.json @@ -47,6 +47,7 @@ "@mui/lab": "5.0.0-alpha.80", "@mui/material": "^5.10.2", "@mui/system": "^5.10.2", + "@mui/utils": "^5.10.16", "@mui/x-data-grid": "^5.16.0", "@mui/x-date-pickers": "5.0.0-beta.2", "@vitejs/plugin-react": "^1.3.2", diff --git a/frontend/client-portal/pnpm-lock.yaml b/frontend/client-portal/pnpm-lock.yaml index f4c3bca4..7e4cba0c 100755 --- a/frontend/client-portal/pnpm-lock.yaml +++ b/frontend/client-portal/pnpm-lock.yaml @@ -15,6 +15,7 @@ specifiers: '@mui/lab': 5.0.0-alpha.80 '@mui/material': ^5.10.2 '@mui/system': ^5.10.2 + '@mui/utils': ^5.10.16 '@mui/x-data-grid': ^5.16.0 '@mui/x-date-pickers': 5.0.0-beta.2 '@types/lodash': ^4.14.184 @@ -85,6 +86,7 @@ dependencies: '@mui/lab': 5.0.0-alpha.80_vli2fzedtzvbon5ke6iprktoka '@mui/material': 5.10.2_mm2isjkwxtdnw3xlwl5r7b6ile '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u + '@mui/utils': 5.10.16_react@17.0.2 '@mui/x-data-grid': 5.16.0_r4jqxufjb3aftjrjm24vhpn4hm '@mui/x-date-pickers': 5.0.0-beta.2_y3fv7pzpxqpbmxcbzsros3kjnu '@vitejs/plugin-react': 1.3.2 @@ -1455,14 +1457,20 @@ packages: engines: {node: '>=6.9.0'} dependencies: core-js-pure: 3.25.0 - regenerator-runtime: 0.13.9 + regenerator-runtime: 0.13.11 dev: true /@babel/runtime/7.18.9: resolution: {integrity: sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==} engines: {node: '>=6.9.0'} dependencies: - regenerator-runtime: 0.13.9 + regenerator-runtime: 0.13.11 + + /@babel/runtime/7.20.6: + resolution: {integrity: sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 /@babel/template/7.18.10: resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} @@ -1554,7 +1562,7 @@ packages: '@babel/core': 7.18.13 '@babel/helper-module-imports': 7.18.6 '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.13 - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@emotion/hash': 0.9.0 '@emotion/memoize': 0.8.0 '@emotion/serialize': 1.1.0 @@ -1838,10 +1846,10 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@emotion/is-prop-valid': 1.2.0 '@mui/types': 7.1.5_@types+react@17.0.48 - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@popperjs/core': 2.11.6 '@types/react': 17.0.48 clsx: 1.2.1 @@ -1862,10 +1870,10 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@emotion/is-prop-valid': 1.2.0 '@mui/types': 7.1.5_@types+react@17.0.48 - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@popperjs/core': 2.11.6 '@types/react': 17.0.48 clsx: 1.2.1 @@ -1924,7 +1932,7 @@ packages: '@mui/base': 5.0.0-alpha.79_sk3eihvpffgp52mstba5zhq3vu '@mui/material': 5.10.2_mm2isjkwxtdnw3xlwl5r7b6ile '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@mui/x-date-pickers': 5.0.0-alpha.0_ktjudfp74mtqzmc2e56ri5vvri '@types/react': 17.0.48 clsx: 1.2.1 @@ -1964,7 +1972,7 @@ packages: '@mui/core-downloads-tracker': 5.10.2 '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u '@mui/types': 7.1.5_@types+react@17.0.48 - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@types/react': 17.0.48 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 @@ -1986,8 +1994,8 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.18.9 - '@mui/utils': 5.9.3_react@17.0.2 + '@babel/runtime': 7.20.6 + '@mui/utils': 5.10.16_react@17.0.2 '@types/react': 17.0.48 prop-types: 15.8.1 react: 17.0.2 @@ -2006,7 +2014,7 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@emotion/cache': 11.10.3 '@emotion/react': 11.10.0_u65opluo5sjgh3eblfliq6jsnm '@emotion/styled': 11.10.0_mpqqkyoc5j3vv2sg2f5vubqi7u @@ -2037,7 +2045,7 @@ packages: '@mui/private-theming': 5.9.3_skqlhrap4das3cz5b6iqdn2lqi '@mui/styled-engine': 5.10.2_2av45khroaevmcc27aulpaf2sy '@mui/types': 7.1.5_@types+react@17.0.48 - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@types/react': 17.0.48 clsx: 1.2.1 csstype: 3.1.0 @@ -2056,13 +2064,13 @@ packages: '@types/react': 17.0.48 dev: false - /@mui/utils/5.9.3_react@17.0.2: - resolution: {integrity: sha512-l0N5bcrenE9hnwZ/jPecpIRqsDFHkPXoFUcmkgysaJwVZzJ3yQkGXB47eqmXX5yyGrSc6HksbbqXEaUya+siew==} + /@mui/utils/5.10.16_react@17.0.2: + resolution: {integrity: sha512-3MB/SGsgiiu9Z55CFmAfiONUoR7AAue/H4F6w3mc2LnhFQCsoVvXhioDPcsiRpUMIQr34jDPzGXdCuqWooPCXQ==} engines: {node: '>=12.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@types/prop-types': 15.7.5 '@types/react-is': 17.0.3 prop-types: 15.8.1 @@ -2082,7 +2090,7 @@ packages: '@babel/runtime': 7.18.9 '@mui/material': 5.10.2_mm2isjkwxtdnw3xlwl5r7b6ile '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 clsx: 1.2.1 prop-types: 15.8.1 react: 17.0.2 @@ -2117,7 +2125,7 @@ packages: '@date-io/moment': 2.15.0 '@mui/material': 5.10.2_mm2isjkwxtdnw3xlwl5r7b6ile '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 clsx: 1.2.1 date-fns: 2.29.2 prop-types: 15.8.1 @@ -2165,7 +2173,7 @@ packages: '@emotion/styled': 11.10.0_mpqqkyoc5j3vv2sg2f5vubqi7u '@mui/material': 5.10.2_mm2isjkwxtdnw3xlwl5r7b6ile '@mui/system': 5.10.2_ynexvg6j2j66sbh5giu7mkkh7u - '@mui/utils': 5.9.3_react@17.0.2 + '@mui/utils': 5.10.16_react@17.0.2 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 date-fns: 2.29.2 @@ -2708,7 +2716,7 @@ packages: resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} engines: {node: '>=6.0'} dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@babel/runtime-corejs3': 7.18.9 dev: true @@ -2798,7 +2806,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 cosmiconfig: 7.0.1 resolve: 1.22.1 @@ -3192,7 +3200,7 @@ packages: /dom-helpers/5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 csstype: 3.1.0 dev: false @@ -5100,7 +5108,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -5127,13 +5135,13 @@ packages: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} dev: true - /regenerator-runtime/0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} /regenerator-transform/0.15.0: resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==} dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 dev: true /regexp.prototype.flags/1.4.3: @@ -5846,7 +5854,7 @@ packages: '@apideck/better-ajv-errors': 0.3.6_ajv@8.11.0 '@babel/core': 7.18.13 '@babel/preset-env': 7.18.10_@babel+core@7.18.13 - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.20.6 '@rollup/plugin-babel': 5.3.1_2uin6pbxavst3oir53roxbd5qi '@rollup/plugin-node-resolve': 11.2.1_rollup@2.78.1 '@rollup/plugin-replace': 2.4.2_rollup@2.78.1 diff --git a/frontend/dashboard/src/@types/paginated-data.ts b/frontend/dashboard/src/@types/paginated-data.ts index 738f8785..eca6d930 100755 --- a/frontend/dashboard/src/@types/paginated-data.ts +++ b/frontend/dashboard/src/@types/paginated-data.ts @@ -12,3 +12,18 @@ export type LaravelPaginatedData = { to?: number; total: number; } + +export const LaravelPaginatedDataDefault = { + 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 +} \ No newline at end of file diff --git a/frontend/dashboard/src/components/LaravelTable.tsx b/frontend/dashboard/src/components/LaravelTable.tsx new file mode 100644 index 00000000..18325952 --- /dev/null +++ b/frontend/dashboard/src/components/LaravelTable.tsx @@ -0,0 +1,28 @@ +import { Card, Paper, TableContainer } from "@mui/material"; +import { LaravelPaginatedData } from "../@types/paginated-data"; +import BasePagination from "./BasePagination"; + +type LaravelTableProps = { + isLoading: boolean; + lastRequest: number; + data: LaravelPaginatedData; + handlePageChange: void; + TableContent: any; +}; + +function LaravelTable(props: LaravelTableProps) { + return ( + + + { props.TableContent } + + + { !props.isLoading ? + () : + (
) + } +
+ ) +} + +export default LaravelTable \ No newline at end of file diff --git a/frontend/dashboard/src/components/MuiDialog.tsx b/frontend/dashboard/src/components/MuiDialog.tsx new file mode 100644 index 00000000..54ee1110 --- /dev/null +++ b/frontend/dashboard/src/components/MuiDialog.tsx @@ -0,0 +1,56 @@ +import { Dialog, DialogTitle, DialogContent, Stack, Typography, IconButton } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { ReactElement } from 'react'; +import Iconify from './Iconify'; + +// ---------------------------------------------------------------------- + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + maxWidth?: string; +}; + +// ---------------------------------------------------------------------- + +const MuiDialog = ({ title, openDialog, setOpenDialog, content, maxWidth }: MuiDialogProps) => { + const handleClose = () => { + setOpenDialog(false); + }; + + let maxWidthDialog = 'md'; + + if (maxWidth) { + maxWidthDialog = maxWidth; + } + + return ( + + + + {title?.icon ? ( + + + {title?.name} + + ) : ( + {title?.name ? title?.name : ''} + )} + + + + + + + {content ? content : 'Testing Content Dialog'} + + + ); +}; + +export default MuiDialog; diff --git a/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx b/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx new file mode 100644 index 00000000..3d1662af --- /dev/null +++ b/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx @@ -0,0 +1,275 @@ +// @mui +import { styled } from '@mui/material/styles'; +import { + Typography, + LinearProgress, + linearProgressClasses, + Stack, + FormControlLabel, + TableRow, + TableCell, + Table, + TableBody, + TextField, + Button, +} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import Checkbox from '@mui/material/Checkbox'; +// components +import { FormProvider, RHFTextField } from '../../components/hook-form'; +// React +import React, { ReactElement, useEffect, useState } from 'react'; +import { fCurrency } from '../../utils/formatNumber'; +// yup +import * as Yup from 'yup'; +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import MuiDialog from '../MuiDialog'; +import { Member } from '../../@types/member'; +import { LaravelPaginatedDataDefault } from '../../@types/paginated-data'; +import DataTable from '../LaravelTable'; +import axios from '../../utils/axios'; + +// ---------------------------------------------------------------------- + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + onSelect?: Function; +}; + +type FormValuesProps = { + topup: string; +}; + +// ---------------------------------------------------------------------- + +const testData = { + companyName: 'PT. Aman Mineral', + policyNumber: 12345678, + totalMembers: 50, + totalCases: 100, + totalPersen: 75, + myLimit: '375.000.000', + totalLimit: 500000000, +}; + +const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ + height: 10, + borderRadius: 6, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 6, + background: 'linear-gradient(270deg, #19BBBB 38.42%, #FF9565 76.21%, #FE7253 104.02%)', + }, +})); + +// ---------------------------------------------------------------------- + +export default function MemberSelectDialog({ + openDialog, + setOpenDialog, + onSelect +}: MuiDialogProps) { + const [dataTableLoading, setDataTableLoading] = useState(false) + const [dataTableData, setDataTableData] = useState(LaravelPaginatedDataDefault) + const [searchFilter, setSearchFilter] = useState({ + search: '' + }) + + const handleSearchChange = (event: any) => { + setSearchFilter({...searchFilter, ...{search: event.target.value}}) + } + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : {}; + const response = await axios.get('/members', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + } + + const applyFilter = async (searchFilter: {search: string}) => { + await loadDataTableData(searchFilter); + } + + const handleFilterSubmit = (event: any) => { + event.preventDefault(); + applyFilter(searchFilter) + } + + useEffect(() => { + console.log('effect openDialog'); + if (openDialog === false) { + } + }, [openDialog]); + + const handlePageChange = () => { + console.log('handle change') + } + + // Called on every row to map the data to the columns + function createData(member: Member): Member { + return { + ...member, + }; + } + + const Row = (props: { row: ReturnType, onSelect }) => ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)} + > + {open ? : } + + */} + {/* { columns.map((column, index) => + { row[column.id] ?? '-' } + ) } */} + {/* TODO FIX DISPLAY DATA FROM RELATION */} + + {/* { id: 'member_id', label: 'MemberID', minWidth: 100, align: "left" }, + { id: 'principal_id', label: 'Mapping ID', minWidth: 100, align: "left" }, + { id: 'nik', label: 'NIK', minWidth: 100, align: "left" }, + { id: 'current_policy.policy_number', label: 'Policy Number', minWidth: 100, align: "left" }, + { id: 'effective_date', label: 'Effective Date', minWidth: 100, align: "left" }, + { id: 'name', label: 'Name', minWidth: 100, align: "left" }, + { id: 'nric', label: 'NRIC', minWidth: 100, align: "left" }, + { id: 'email', label: 'E-mail', minWidth: 100, align: "left" }, + { id: 'plan_id', label: 'PlanID', minWidth: 100, align: "left" }, + { id: 'termination_date', label: 'Termination Date', minWidth: 100, align: 'right' }, + { id: 'activation_date', label: 'Activation Date', minWidth: 100, align: "right" }, + */} + {props.row.member_id} + {props.row.principal_id} + {props.row.employeds?.[0]?.nik} + {props.row.current_policy?.policy_id} + {props.row.current_policy?.start} + {props.row.name} + {props.row.nric} + {props.row.email} + {props.row.current_plan?.code} + {props.row.current_policy?.start} + {props.row.current_policy?.end} + + + {/* COLLAPSIBLE ROW */} + {/* + + + + + No Extra Data + + + {false && } + + + */} + + ); + + const headStyle = { + fontWeight: 'bold', + }; + const TableContent = () => ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + + + Type + + + Code + + + Name + + + Version + + + Status + + + Action + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + + const getContent = () => ( + +
+ + + } + /> +
+ ); + + return ( + + ); +} diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index a7191de0..1072023d 100755 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -62,9 +62,10 @@ const navConfig = [ }, { title: 'CASE MANAGEMENT', - children: [ - { title: 'Request', path: '/case-request' }, - ], + path: '/claims', + // children: [ + // { title: 'Request', path: '/case-request' }, + // ], }, { title: 'CUSTOMER SERVICES', diff --git a/frontend/dashboard/src/pages/Claims/Create.tsx b/frontend/dashboard/src/pages/Claims/Create.tsx new file mode 100755 index 00000000..948fd347 --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/Create.tsx @@ -0,0 +1,413 @@ +import * as Yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Autocomplete, Button, Card, Collapse, Divider, Grid, Stack, Table, TableBody, TableCell, TableRow, TextField, Typography } from '@mui/material'; +import { Controller, useForm } from 'react-hook-form'; +import { useParams, useNavigate } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs'; +import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form'; +import Page from '../../components/Page'; +import useSettings from '../../hooks/useSettings'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; +import { styled } from '@mui/system'; +import axios from '../../utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../utils/formatNumber'; +import Iconify from '../../components/Iconify'; + +export default function ClaimsCreate() { + const navigate = useNavigate(); + + const [member, setMember] = useState(); + const selectedMemberDisplay = useRef(null); + + const NewClaimSchema = Yup.object().shape({ + member_id: Yup.string().required('Please select Member'), + total_claim: Yup.number().typeError('Claim should be a number').min(1, 'Claim cannot 0').required('Total Claim is required'), + diagnosis: Yup.object().required('Choose Diagnosis'), + benefit: Yup.object().required('Please Select Benefit') + }); + + const defaultValues = useMemo( + () => ({ + member_id: null, + benefit_id: null, + diagnosis_id: null, + total_claim: 0, + benefit: null + }), + [] + ); + + const methods = useForm({ + resolver: yupResolver(NewClaimSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const onSubmit = async (data: any) => { + axios.post('claims', getValues()) + .then(function(res) { + console.log('SUCCESS', res) + enqueueSnackbar('Success Creating Claim', { variant: 'success' }) + navigate('/claims'); + }) + .catch(function (err) { + console.log('ERROR CUK', err) + enqueueSnackbar('Failed Creating Claim : '+ err.response.data.message, { variant: 'error' }) + }) + }; + + const [memberBenefits, setMemberBenefits] = useState([]); + const getMemberBenefits = (member) => { + axios.get(`members/${member.id}/benefits`) + .then( (res) => { + setMemberBenefits(res.data); + }) + .catch( (err) => { + enqueueSnackbar('Failed getting member benefits', { variant: 'error' }) + }) + } + + const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); + + const memberSelected = (selectedMember: any) => { + // Reset Selected Benefit + setMemberBenefits([]); + setValue('benefit_id', null); + setValue('benefit', null); + + setMember(selectedMember); + setValue('member_id', selectedMember.id) + + getMemberBenefits(selectedMember) + }; + + const [diagnosis, setDiagnosis] = useState([]); + + const searchDiagnosis = (search) => { + axios.get('master/diagnosis/search', {params: {search}}) + .then(function(res) { + setDiagnosis(res.data); + }) + } + + useEffect(() => { // Trigger First Search + axios.get('master/diagnosis/search') + .then(function(res) { + setDiagnosis(res.data); + }) + + }, []) + + const [isEligible, setIsEligible] = useState(null) + + const [isCheckingLimit, setIsCheckingLimit] = useState(false) + const checkLimit = (event) => { + event.preventDefault(); + + console.log(getValues('diagnosis_id')) + if (!member || !getValues('diagnosis_id')) { + enqueueSnackbar('Please Check the Data', { variant: 'error' }) + + return false; + } + + setIsCheckingLimit(true) + axios.post('check-limit', { + 'member_id' : member.id, + 'diagnosis' : getValues('diagnosis_id'), + 'total_claim' : getValues('total_claim') + }) + .then((res) => { + setIsEligible(true) + }) + .catch((err) => { + enqueueSnackbar('Failed Checking Limit : ' + err.message ?? '', { variant: 'error' }) + }) + .then(() => { + setIsCheckingLimit(false) + }) + } + + const headStyle = { + fontWeight: 'bold' + }; + + return ( + + + + + + + + + + Member + + + + {setIsMemberDialogOpen(true)}} + /> + + {/* + + */} + + + { member && ( + + + + + + + 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) + // }} + /> + )} + /> + )} + /> + + ( + + 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 + + + )} + + + + + + { isEligible === true ? ( + + Create Claim + + ) : ( + + Check Limit + + )} + +
+
+
+
+
+ + +
+ ); +} diff --git a/frontend/dashboard/src/pages/Claims/Index.tsx b/frontend/dashboard/src/pages/Claims/Index.tsx new file mode 100755 index 00000000..15c2d7d8 --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +import Page from "../../components/Page"; +import List from "./List"; + + + +export default function Claims() { + + const pageTitle = 'Claim'; + return ( + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Claims/List.tsx b/frontend/dashboard/src/pages/Claims/List.tsx new file mode 100755 index 00000000..d764d8df --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/List.tsx @@ -0,0 +1,266 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, MenuItem, Table, TableBody, TableCell, TableRow, TextField, Typography, Stack, Menu, ButtonGroup, Link } 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, useEffect, useRef, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +// components +import axios from '../../utils/axios'; +import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data'; +import DataTable from '../../components/LaravelTable'; +import { fCurrency } from '../../utils/formatNumber'; + +export default function List() { + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({search: searchText }); // Trigger to Parent + } + + useEffect(() => { // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []) + + 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 ( +
+ + + + + + +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState(LaravelPaginatedDataDefault); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/claims', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + } + + const applyFilter = async (searchFilter: {search: string}) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + } + + const handlePageChange = (event : ChangeEvent, value: number) : void => { + const filter = Object.fromEntries([...searchParams.entries(), ["page", value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + const headStyle = { + fontWeight: 'bold', + }; + + // Called on every row to map the data to the columns + function createData( data: any ): any { + return { + ...data, + } + } + + {/* ------------------ TABLE ROW ------------------ */} + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)} + > + {open ? : } + + + {row.code} + {row.member?.full_name} + {row.plan?.code} + {row.benefit?.code} + ({row.diagnosis?.code}) {row.diagnosis?.name} + {fCurrency(row.total_claim)} + + {/* */} + + {/* COLLAPSIBLE ROW */} + + + + + + Description : {row.description} + + + + + + + ); + } + {/* ------------------ END TABLE ROW ------------------ */} + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + + Code + Member Name + Plan + Benefit + Diagnosis + Total Claim + {/* Action */} + + + {/* ------------------ END TABLE HEADER ------------------ */} + + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? + ( + + + Loading + + + ) : ( + dataTableData.data.length === 0 ? + ( + + + No Data + + + ) : ( + + {dataTableData.data.map(row => ( + + ))} + + ) + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ) + } + + return ( + + + + } + /> + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Index.tsx b/frontend/dashboard/src/pages/Corporates/Index.tsx index 5c0ea756..6f99353a 100755 --- a/frontend/dashboard/src/pages/Corporates/Index.tsx +++ b/frontend/dashboard/src/pages/Corporates/Index.tsx @@ -398,6 +398,16 @@ export default function Corporates() { ? fCurrency(row.current_policy?.minimal_deposit_net) : '-'} + + + Corporate Limit + + + :{' '} + {row.current_policy + ? fCurrency(row.current_policy?.limit_balance) + : '-'} + diff --git a/frontend/dashboard/src/pages/Corporates/Member/List.tsx b/frontend/dashboard/src/pages/Corporates/Member/List.tsx index 371349fa..b60c8ec4 100755 --- a/frontend/dashboard/src/pages/Corporates/Member/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Member/List.tsx @@ -268,7 +268,7 @@ export default function CorporatePlanList() { {row.name} {row.nric} {row.email} - {row.plan_id} + {row.current_plan?.code} {row.current_policy?.start} {row.current_policy?.end} {( row.active ? () diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 0f977c1e..bc126492 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -205,6 +205,20 @@ export default function Router() { path: 'master/formularium/create', element: , }, + + + { + path: 'claims', + element: , + }, + { + path: 'claims/create', + element: + }, + { + path: 'claims/:id', + element: + } ] }, // { @@ -287,3 +301,6 @@ const CorporateServicesCreate = Loadable(lazy(() => import('../pages/Corporates/ const CorporateHospitals = Loadable(lazy(() => import('../pages/Corporates/Hospital/Index'))); const CorporateClaimHistories = Loadable(lazy(() => import('../pages/Corporates/ClaimHistory/Index'))); + +const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); +const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/Create'))); \ No newline at end of file