Merge remote-tracking branch 'origin/master' into mhmfajar
This commit is contained in:
124
Modules/Internal/Http/Controllers/Api/ClaimController.php
Normal file
124
Modules/Internal/Http/Controllers/Api/ClaimController.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Internal\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Benefit;
|
||||
use App\Models\Claim;
|
||||
use App\Models\Icd;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Contracts\Support\Renderable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Internal\Services\ClaimService;
|
||||
|
||||
class ClaimController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* @return Renderable
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$claims = Claim::with([
|
||||
'member',
|
||||
'diagnosis',
|
||||
'plan',
|
||||
'benefit'
|
||||
])
|
||||
->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;
|
||||
}
|
||||
}
|
||||
205
Modules/Internal/Http/Controllers/Api/CorporateMemberController.php
Executable file
205
Modules/Internal/Http/Controllers/Api/CorporateMemberController.php
Executable file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
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 CorporateMemberController 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)
|
||||
{
|
||||
$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,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
130
Modules/Internal/Http/Controllers/Api/MemberController.php
Executable file → Normal file
130
Modules/Internal/Http/Controllers/Api/MemberController.php
Executable file → Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\MemberController as ApiMemberController;
|
||||
use Modules\Internal\Http\Controllers\Api\AuthController;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Internal\Http\Controllers\Api\BenefitController;
|
||||
use Modules\Internal\Http\Controllers\Api\ClaimController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporateBenefitController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporateController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporateFormulariumController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporateMemberController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporatePlanController;
|
||||
use Modules\Internal\Http\Controllers\Api\CorporateServiceController;
|
||||
use Modules\Internal\Http\Controllers\Api\DiagnosisController;
|
||||
@@ -67,8 +70,8 @@ Route::prefix('internal')->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']);
|
||||
|
||||
115
Modules/Internal/Services/ClaimService.php
Normal file
115
Modules/Internal/Services/ClaimService.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Internal\Services;
|
||||
|
||||
use App\Models\Claim;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ClaimService
|
||||
{
|
||||
public static function checkMemberEligibility($member, $benefit, $diagnosis, $totalClaim = 0)
|
||||
{
|
||||
$currentPlan = $member->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'])
|
||||
|
||||
@@ -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
|
||||
|
||||
81
app/Models/Claim.php
Normal file
81
app/Models/Claim.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Claim extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'member_id',
|
||||
'diagnosis_id',
|
||||
'total_claim',
|
||||
'currency',
|
||||
'plan_id',
|
||||
'benefit_id',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
public static $status = [
|
||||
'draft' => '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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
13
app/Models/CorporateManager.php
Normal file
13
app/Models/CorporateManager.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CorporateManager extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'corporate_manager';
|
||||
}
|
||||
@@ -29,11 +29,29 @@ class CorporatePolicy extends Model
|
||||
'active',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'latestLimitJournal'
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'limit_balance'
|
||||
];
|
||||
|
||||
public function corporate()
|
||||
{
|
||||
return $this->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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
37
app/Models/LimitJournal.php
Normal file
37
app/Models/LimitJournal.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LimitJournal extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'journalable',
|
||||
'previous_balance',
|
||||
'total_credit',
|
||||
'total_debit',
|
||||
'type',
|
||||
'balance',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function journalable()
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -165,4 +165,9 @@ class Plan extends Model
|
||||
// {
|
||||
// return $this->belongsTo(Corporate::class);
|
||||
// }
|
||||
|
||||
public function benefits()
|
||||
{
|
||||
return $this->hasMany(Benefit::class, 'plan_code', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
44
app/Services/ClaimService.php
Normal file
44
app/Services/ClaimService.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Claim;
|
||||
use App\Models\Icd;
|
||||
use App\Models\Member;
|
||||
use Carbon\Carbon;
|
||||
use Str;
|
||||
|
||||
class ClaimService{
|
||||
|
||||
public function storeClaim($member, $icd, $benefit, $totalClaim)
|
||||
{
|
||||
$claim = Claim::create([
|
||||
'code' => 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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('claims', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('limit_journals', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
36
database/seeders/DummyClaimSeeder.php
Normal file
36
database/seeders/DummyClaimSeeder.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Icd;
|
||||
use App\Models\Member;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Str;
|
||||
|
||||
class DummyClaimSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$members = Member::limit(10)->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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
62
frontend/client-portal/pnpm-lock.yaml
generated
62
frontend/client-portal/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
28
frontend/dashboard/src/components/LaravelTable.tsx
Normal file
28
frontend/dashboard/src/components/LaravelTable.tsx
Normal file
@@ -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 (
|
||||
<Card>
|
||||
<TableContainer component={Paper}>
|
||||
{ props.TableContent }
|
||||
</TableContainer>
|
||||
|
||||
{ !props.isLoading ?
|
||||
(<BasePagination paginationData={props.data} onPageChange={props.handlePageChange}/>) :
|
||||
(<div></div>)
|
||||
}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default LaravelTable
|
||||
56
frontend/dashboard/src/components/MuiDialog.tsx
Normal file
56
frontend/dashboard/src/components/MuiDialog.tsx
Normal file
@@ -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 (
|
||||
<Dialog open={openDialog} onClose={handleClose} fullWidth={true} maxWidth={maxWidthDialog}>
|
||||
<DialogTitle sx={{ backgroundColor: '#19BBBB', color: '#FFF', padding: 2 }}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between">
|
||||
{title?.icon ? (
|
||||
<Stack direction="row">
|
||||
<Iconify icon={title?.icon} width={25} height={25} sx={{ marginRight: '10px' }} />
|
||||
<Typography variant="h6">{title?.name}</Typography>
|
||||
</Stack>
|
||||
) : (
|
||||
<Typography variant="h6">{title?.name ? title?.name : ''}</Typography>
|
||||
)}
|
||||
<IconButton sx={{ color: '#FFF' }} onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ backgroundColor: '#F9FAFB' }}>
|
||||
{content ? content : 'Testing Content Dialog'}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuiDialog;
|
||||
275
frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx
Normal file
275
frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx
Normal file
@@ -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<typeof createData>, onSelect }) => (
|
||||
<React.Fragment>
|
||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||
{/* <TableCell>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
||||
</IconButton>
|
||||
</TableCell> */}
|
||||
{/* { columns.map((column, index) =>
|
||||
<TableCell key={ index } align={ column.align } minwidth={ column.minWidth }>{ row[column.id] ?? '-' }</TableCell>
|
||||
) } */}
|
||||
{/* 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" },
|
||||
*/}
|
||||
<TableCell align="left">{props.row.member_id}</TableCell>
|
||||
<TableCell align="left">{props.row.principal_id}</TableCell>
|
||||
<TableCell align="left">{props.row.employeds?.[0]?.nik}</TableCell>
|
||||
<TableCell align="left">{props.row.current_policy?.policy_id}</TableCell>
|
||||
<TableCell align="left">{props.row.current_policy?.start}</TableCell>
|
||||
<TableCell align="left">{props.row.name}</TableCell>
|
||||
<TableCell align="left">{props.row.nric}</TableCell>
|
||||
<TableCell align="left">{props.row.email}</TableCell>
|
||||
<TableCell align="left">{props.row.current_plan?.code}</TableCell>
|
||||
<TableCell align="left">{props.row.current_policy?.start}</TableCell>
|
||||
<TableCell align="left">{props.row.current_policy?.end}</TableCell>
|
||||
<TableCell align="right" style={{ position: 'sticky', right: 0}}><Button variant="outlined" color="success" style={{backgroundColor: "#ffffff"}} size="small" onClick={() => {onSelect(props.row); setOpenDialog(false); }}>Select</Button></TableCell>
|
||||
</TableRow>
|
||||
{/* COLLAPSIBLE ROW */}
|
||||
{/* <TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={10}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ borderBottom: 1 }}>
|
||||
<Typography variant="body2" gutterBottom component="div">
|
||||
No Extra Data
|
||||
</Typography>
|
||||
</Box>
|
||||
{false && <Box sx={{ margin: 1 }} />}
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const headStyle = {
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
const TableContent = () => (
|
||||
<Table aria-label="collapsible table">
|
||||
{/* ------------------ TABLE HEADER ------------------ */}
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left" />
|
||||
<TableCell style={headStyle} align="left">
|
||||
Type
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Code
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Version
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="right">
|
||||
Status
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="right">
|
||||
Action
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
{/* ------------------ END TABLE HEADER ------------------ */}
|
||||
|
||||
{/* ------------------ TABLE ROW ------------------ */}
|
||||
{dataTableLoading ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
Loading
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : dataTableData.data.length === 0 ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
No Data
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
<TableBody>
|
||||
{dataTableData.data.map((row) => (
|
||||
<Row key={row.id} row={row} onSelect/>
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
{/* ------------------ END TABLE ROW ------------------ */}
|
||||
</Table>
|
||||
);
|
||||
|
||||
const getContent = () => (
|
||||
<Stack spacing={1} marginTop={2}>
|
||||
<form onSubmit={handleFilterSubmit}>
|
||||
<TextField label="Search" variant="outlined" fullWidth onChange={handleSearchChange} value={searchFilter.search} autoFocus/>
|
||||
</form>
|
||||
<DataTable
|
||||
isLoading={dataTableLoading}
|
||||
lastRequest={0}
|
||||
data={dataTableData}
|
||||
handlePageChange={handlePageChange}
|
||||
TableContent={<TableContent />}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
return (
|
||||
<MuiDialog
|
||||
title={{name: "Select Member"}}
|
||||
openDialog={openDialog}
|
||||
setOpenDialog={setOpenDialog}
|
||||
content={getContent()}
|
||||
maxWidth="xl"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
413
frontend/dashboard/src/pages/Claims/Create.tsx
Executable file
413
frontend/dashboard/src/pages/Claims/Create.tsx
Executable file
@@ -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<HTMLInputElement>(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<boolean|null>(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 (
|
||||
<Page title="Create Claim" sx={{ mx: 2 }}>
|
||||
<HeaderBreadcrumbs
|
||||
heading={'Create Claim'}
|
||||
links={[
|
||||
{ name: 'Dashboard', href: '/dashboard' },
|
||||
{
|
||||
name: 'Claims',
|
||||
href: '/claims',
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
href: '/claims/create',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Card sx={{ p: 2 }}>
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={3}>
|
||||
|
||||
<Typography variant="h6">Member</Typography>
|
||||
|
||||
<Stack spacing={2} direction="row">
|
||||
<Grid item xs={12}>
|
||||
<RHFTextField
|
||||
name="member_id"
|
||||
label="Member"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={member?.name || ''}
|
||||
ref={selectedMemberDisplay}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
onClick={() => {setIsMemberDialogOpen(true)}}
|
||||
/>
|
||||
</Grid>
|
||||
{/* <Grid item xs={2}>
|
||||
<Button variant="outlined" fullWidth sx={{ p: 1.8 }} onClick={() => {
|
||||
setIsMemberDialogOpen(true)
|
||||
}}>
|
||||
{member ? 'Change' : 'Search'}
|
||||
</Button>
|
||||
</Grid> */}
|
||||
</Stack>
|
||||
|
||||
{ member && (
|
||||
<Stack>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Table border="light-700">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Name</TableCell>
|
||||
<TableCell align="left">{member.full_name}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">DOB</TableCell>
|
||||
<TableCell align="left">{member.birth_date} ({member.age + ' years'})</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Marital Status</TableCell>
|
||||
<TableCell align="left">{member.marital_status}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Record Type</TableCell>
|
||||
<TableCell align="left">{member.record_type}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Principal ID</TableCell>
|
||||
<TableCell align="left">{member.principal_id} ({member.relation_with_principal})</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Table border="light-700">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Plan</TableCell>
|
||||
<TableCell align="left">{member.current_plan.code}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Active</TableCell>
|
||||
<TableCell align="left">{member.current_plan.start} - {member.current_plan.end} (Active)</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Corporate Limit</TableCell>
|
||||
<TableCell align="left">{fCurrency(0)} / {fCurrency(member.current_plan.limit_rules)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left">Plan Usage</TableCell>
|
||||
<TableCell align="left">{fCurrency(0)} / {fCurrency(member.current_plan.limit_rules)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
name="benefit"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Autocomplete
|
||||
options={memberBenefits}
|
||||
getOptionLabel={(option) =>
|
||||
option ? `#${option.id} (${option.code}) ${option.description}` : ''
|
||||
}
|
||||
value={value || ''}
|
||||
onChange={(event: any, newValue: any) => {
|
||||
setValue('benefit_id', newValue?.id)
|
||||
onChange(newValue);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
name="benefit"
|
||||
{...params}
|
||||
label="Benefit"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
// onKeyPress={(event) => {
|
||||
// if (event.key === 'Enter')
|
||||
// searchDiagnosis(event.target.value)
|
||||
// }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="diagnosis"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Autocomplete
|
||||
options={diagnosis}
|
||||
getOptionLabel={(option) =>
|
||||
option ? `(${option.code}) ${option.name}` : ''
|
||||
}
|
||||
value={value || ''}
|
||||
onChange={(event: any, newValue: any) => {
|
||||
setValue('diagnosis_id', newValue?.id)
|
||||
// setValue('diagnosis', newValue)
|
||||
onChange(newValue);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
name="diagnosis"
|
||||
{...params}
|
||||
label="Diagnosis"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onKeyPress={(event) => {
|
||||
if (event.key === 'Enter')
|
||||
searchDiagnosis(event.target.value)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{ isCheckingLimit && (
|
||||
<Stack sx={{ backgroundColor: 'gray', paddingY: 1, paddingX: 1.5, mb: 2, borderRadius: '3-xl' }}>
|
||||
{/* Checking */}
|
||||
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
|
||||
<Iconify
|
||||
icon="bxs:info-circle"
|
||||
width={12}
|
||||
height={13}
|
||||
sx={{ color: '#424242', marginRight: '6px' }}
|
||||
/>
|
||||
<Typography variant="caption" component="span">
|
||||
Please Wait, Checking Eligibilty
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
{ false && isCheckingLimit == false && isEligible == null && (
|
||||
<Stack sx={{ backgroundColor: 'gray', paddingY: 1, paddingX: 1.5, mb: 2, borderRadius: '3-xl' }}>
|
||||
{/* No Data Selected */}
|
||||
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
|
||||
<Iconify
|
||||
icon="bxs:info-circle"
|
||||
width={12}
|
||||
height={13}
|
||||
sx={{ color: '#424242', marginRight: '6px' }}
|
||||
/>
|
||||
<Typography variant="caption" component="span">
|
||||
Please Select Diagnosis !
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
{ (!isCheckingLimit && isEligible !== null) && isEligible && (
|
||||
<Stack sx={{ backgroundColor: '#B2E8E8', paddingY: 1, paddingX: 1.5, mb: 2, borderRadius: '3-xl' }}>
|
||||
{/* Eligible */}
|
||||
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
|
||||
<Iconify
|
||||
icon="bxs:lock-alt"
|
||||
width={12}
|
||||
height={13}
|
||||
sx={{ color: '#424242', marginRight: '6px' }}
|
||||
/>
|
||||
<Typography variant="caption" component="span">
|
||||
Diagnosis is Eligible
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography sx={{ typography: 'caption', color: '#637381' }}>
|
||||
125.000.000 / 125.000.000
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
{ (!isCheckingLimit && isEligible !== null) && !isEligible && (
|
||||
<Stack sx={{ backgroundColor: '#B2E8E8', paddingY: 1, paddingX: 1.5, mb: 2, borderRadius: '3-xl' }}>
|
||||
{/* Not Eligible */}
|
||||
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
|
||||
<Iconify
|
||||
icon="bxs:lock-alt"
|
||||
width={12}
|
||||
height={13}
|
||||
sx={{ color: '#424242', marginRight: '6px' }}
|
||||
/>
|
||||
<Typography variant="caption" component="span">
|
||||
Not Eligible
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography sx={{ typography: 'caption', color: '#637381' }}>
|
||||
125.000.000 / 125.000.000
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
<RHFTextField type="number" name="total_claim" label="Total Claim" />
|
||||
|
||||
{ isEligible === true ? (
|
||||
<LoadingButton onClick={handleSubmit(onSubmit)} variant="contained" color="success" style={{color: '#ffffff'}} size="large" fullWidth={true} loading={isCheckingLimit}>
|
||||
Create Claim
|
||||
</LoadingButton>
|
||||
) : (
|
||||
<LoadingButton onClick={checkLimit} variant="outlined" size="large" fullWidth={true} loading={isCheckingLimit}>
|
||||
Check Limit
|
||||
</LoadingButton>
|
||||
)}
|
||||
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<MemberSelectDialog
|
||||
openDialog={isMemberDialogOpen}
|
||||
setOpenDialog={setIsMemberDialogOpen}
|
||||
onSelect={memberSelected}
|
||||
></MemberSelectDialog>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
30
frontend/dashboard/src/pages/Claims/Index.tsx
Executable file
30
frontend/dashboard/src/pages/Claims/Index.tsx
Executable file
@@ -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 (
|
||||
<Page title={ pageTitle } sx={{ mx: 2}}>
|
||||
|
||||
<HeaderBreadcrumbs
|
||||
heading={ pageTitle }
|
||||
links={[
|
||||
{ name: 'Dashboard', href: '/dashboard' },
|
||||
{
|
||||
name: 'Claim',
|
||||
href: '/claims',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Stack>
|
||||
<List />
|
||||
</Stack>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
266
frontend/dashboard/src/pages/Claims/List.tsx
Executable file
266
frontend/dashboard/src/pages/Claims/List.tsx
Executable file
@@ -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<HTMLInputElement>(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 (
|
||||
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
|
||||
<TextField id="search-input" ref={searchInput} label="Search" variant="outlined" fullWidth onChange={handleSearchChange} value={searchText}/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportForm(props: any) {
|
||||
// IMPORT
|
||||
// Create Button Menu
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const createMenu = Boolean(anchorEl);
|
||||
const importForm = useRef<HTMLInputElement>(null)
|
||||
const [currentImportFileName, setCurrentImportFileName] = useState(null)
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
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 (
|
||||
<div>
|
||||
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||
<SearchInput onSearch={applyFilter}/>
|
||||
<Link href="/claims/create" style={{ textDecoration: "none" }}>
|
||||
<Button
|
||||
variant='outlined'
|
||||
startIcon={<AddIcon />} sx={{ p: 1.8 }}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Link>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Dummy Default Data
|
||||
const [dataTableIsLoading, setDataTableLoading] = useState(true);
|
||||
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(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<typeof createData> }) {
|
||||
const { row } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell align="left">{row.code}</TableCell>
|
||||
<TableCell align="left">{row.member?.full_name}</TableCell>
|
||||
<TableCell align="left">{row.plan?.code}</TableCell>
|
||||
<TableCell align="left">{row.benefit?.code}</TableCell>
|
||||
<TableCell align="left">({row.diagnosis?.code}) {row.diagnosis?.name}</TableCell>
|
||||
<TableCell align="left">{fCurrency(row.total_claim)}</TableCell>
|
||||
|
||||
{/* <TableCell align="right"><Button variant="outlined" color="error" size="small">Disable</Button></TableCell> */}
|
||||
</TableRow>
|
||||
{/* COLLAPSIBLE ROW */}
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ borderBottom: 1 }}>
|
||||
<Typography variant="body2" gutterBottom component="div">
|
||||
Description : {row.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
{/* ------------------ END TABLE ROW ------------------ */}
|
||||
|
||||
function TableContent() {
|
||||
return (
|
||||
<Table aria-label="collapsible table">
|
||||
{/* ------------------ TABLE HEADER ------------------ */}
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left" />
|
||||
<TableCell style={headStyle} align="left">Code</TableCell>
|
||||
<TableCell style={headStyle} align="left">Member Name</TableCell>
|
||||
<TableCell style={headStyle} align="left">Plan</TableCell>
|
||||
<TableCell style={headStyle} align="left">Benefit</TableCell>
|
||||
<TableCell style={headStyle} align="left">Diagnosis</TableCell>
|
||||
<TableCell style={headStyle} align="left">Total Claim</TableCell>
|
||||
{/* <TableCell style={headStyle} align="right">Action</TableCell> */}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
{/* ------------------ END TABLE HEADER ------------------ */}
|
||||
|
||||
|
||||
{/* ------------------ TABLE ROW ------------------ */}
|
||||
{dataTableIsLoading ?
|
||||
(
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">Loading</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
dataTableData.data.length === 0 ?
|
||||
(
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">No Data</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
<TableBody>
|
||||
{dataTableData.data.map(row => (
|
||||
<Row key={row.id} row={row} />
|
||||
))}
|
||||
</TableBody>
|
||||
)
|
||||
)}
|
||||
{/* ------------------ END TABLE ROW ------------------ */}
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<ImportForm />
|
||||
|
||||
<DataTable
|
||||
isLoading={dataTableIsLoading}
|
||||
lastRequest={0}
|
||||
data={dataTableData}
|
||||
handlePageChange={handlePageChange}
|
||||
|
||||
TableContent={<TableContent />}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -398,6 +398,16 @@ export default function Corporates() {
|
||||
? fCurrency(row.current_policy?.minimal_deposit_net)
|
||||
: '-'}
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
Corporate Limit
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
:{' '}
|
||||
{row.current_policy
|
||||
? fCurrency(row.current_policy?.limit_balance)
|
||||
: '-'}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -268,7 +268,7 @@ export default function CorporatePlanList() {
|
||||
<TableCell align="left">{row.name}</TableCell>
|
||||
<TableCell align="left">{row.nric}</TableCell>
|
||||
<TableCell align="left">{row.email}</TableCell>
|
||||
<TableCell align="left">{row.plan_id}</TableCell>
|
||||
<TableCell align="left">{row.current_plan?.code}</TableCell>
|
||||
<TableCell align="left">{row.current_policy?.start}</TableCell>
|
||||
<TableCell align="left">{row.current_policy?.end}</TableCell>
|
||||
{( row.active ? (<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>)
|
||||
|
||||
@@ -205,6 +205,20 @@ export default function Router() {
|
||||
path: 'master/formularium/create',
|
||||
element: <MasterFormulariumCreate />,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: 'claims',
|
||||
element: <Claims />,
|
||||
},
|
||||
{
|
||||
path: 'claims/create',
|
||||
element: <ClaimsCreate />
|
||||
},
|
||||
{
|
||||
path: 'claims/:id',
|
||||
element: <ClaimsCreate />
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
@@ -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')));
|
||||
Reference in New Issue
Block a user