[WIP] Claim Encounters

This commit is contained in:
R
2023-03-24 14:41:02 +07:00
parent 229908e492
commit 7b9a341ccd
23 changed files with 1099 additions and 37 deletions

View File

@@ -77,7 +77,7 @@ class ClaimController extends Controller
'claimRequest', 'claimRequest',
'claimRequest.files', 'claimRequest.files',
'items', 'items',
'items.claim_itemable' 'items.claim_itemable',
]) ])
->findOrFail($id); ->findOrFail($id);

View File

@@ -107,7 +107,12 @@ class ClaimController extends Controller
'claimRequest', 'claimRequest',
'claimRequest.files', 'claimRequest.files',
'items', 'items',
'items.claim_itemable' 'items.claim_itemable',
'encounters',
'encounters.doctors',
'encounters.primaryDiagnoses',
'encounters.primaryDiagnoses.diagnosis',
'encounters.healthcare'
]) ])
->findOrFail($id); ->findOrFail($id);
@@ -150,6 +155,19 @@ class ClaimController extends Controller
return true; return true;
} }
public function updateDetails(Request $request, $id)
{
$request->validate([
'healthcare_id' => 'required',
'doctor_id' => 'required',
'start' => 'required',
'end' => 'required'
]);
$claim = Claim::findOrFail($id);
return $claim;
}
public function updateItems(Request $request, $id) public function updateItems(Request $request, $id)
{ {
@@ -242,8 +260,7 @@ class ClaimController extends Controller
{ {
$claim = Claim::findOrFail($id); $claim = Claim::findOrFail($id);
// TODO Fix this tipu tipu $hospital = $claim->finalEncounter->healthcare ?? null;
$hospital = Organization::where('code', 'ORG000D')->first();
// TODO Fix this tipu tipu // TODO Fix this tipu tipu
$inpationBenefit = $claim->member->currentPlan->benefits()->first(); $inpationBenefit = $claim->member->currentPlan->benefits()->first();
@@ -251,17 +268,17 @@ class ClaimController extends Controller
$pdf = PDF::loadView('pdf.final_log', [ $pdf = PDF::loadView('pdf.final_log', [
'claim' => $claim, 'claim' => $claim,
'member' => $claim->member, 'member' => $claim->member,
'dateOfAdmission' => now(), 'dateOfAdmission' => $claim->start,
'hospital' => $hospital, 'hospital' => $hospital,
'inpationBenefit' => $inpationBenefit 'inpationBenefit' => $inpationBenefit
]); ]);
return $pdf->download('invoice.pdf'); return $pdf->download('Final LOG '.$claim->code.'.pdf');
$view = view('pdf.final_log', [ $view = view('pdf.final_log', [
'claim' => $claim, 'claim' => $claim,
'member' => $claim->member, 'member' => $claim->member,
'dateOfAdmission' => now(), 'dateOfAdmission' => $claim->start,
'hospital' => $hospital, 'hospital' => $hospital,
'inpationBenefit' => $inpationBenefit 'inpationBenefit' => $inpationBenefit
]); ]);

View File

@@ -0,0 +1,281 @@
<?php
namespace Modules\Internal\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Claim;
use App\Models\Encounter;
use App\Models\Practitioner;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Modules\Internal\Transformers\EncounterResource;
class ClaimEncounterController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index()
{
return view('internal::index');
}
/**
* 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, $claim_id)
{
$request->validate([
'service_code' => 'required',
'tanggal_masuk' => 'required',
'tanggal_keluar' => 'required'
]);
$claim = Claim::findOrFail($claim_id);
// return ($request->primary_diagnosis['id']);
// die;
// return $request->toArray();
try {
DB::beginTransaction();
$newEncounterData = [
'status' => 'completed',
'class' => $request->service_code,
'type' => 'Consultation',
'patient_id' => $claim->member->person_id,
'start' => $request->tanggal_masuk,
'end' => $request->tanggal_keluar,
'number_of_bed' => $request->number_of_bed,
'duration_day' => $request->duration_day
];
if ($request->has('healthcare')) {
$newEncounterData['healthcare_id'] = $request->healthcare['id'];
}
// Create New Encounter
$newEncounter = $claim->encounters()->create($newEncounterData);
// --------------------------------------------
// Meta
// TODO Handle if healthcare not primaya
$newEncounter->metas()->updateOrCreate([
'type' => 'MEDRECID',
'system' => 'primaya-his'
], [
'type' => 'MEDRECID',
'system' => 'primaya-his',
'value' => $request->medical_record_number
]);
// ---------------------------------------------
// Handle Diagnosis
if ($request->has('primary_diagnosis') && $request->primary_diagnosis) {
$newEncounter->diagnoses()->create([
'diagnosis_id' => $request->primary_diagnosis['id'],
'type' => 'primary',
'use' => 'discharge',
'source' => 'primecenter',
'description' => 'Batching',
]);
}
if ($request->has('secondary_diagnoses')) {
foreach ($request->secondary_diagnoses as $diagnosis) {
if (!isset($diagnosis['id'])) { // Handle Null Values
continue;
}
$newEncounter->diagnoses()->create([
'diagnosis_id' => $diagnosis['id'],
'type' => 'secondary',
'use' => 'discharge',
'source' => 'primecenter',
'description' => 'Batching',
]);
}
}
// ------------------------------------
// Handle Doctors as primary Doctor
if ($request->has('doctor')) {
$newEncounter->participants()->create([
'type' => 'Doctor',
'participantable_type' => Practitioner::class,
'participantable_id' => $request->doctor['id'],
]);
}
DB::commit();
$newEncounter->load(['diagnoses', 'doctors', 'healthcare']);
return Helper::responseJson(data: EncounterResource::make($newEncounter), message: 'Encounter berhasil ditambahkan');
} catch (\Exception $e) {
DB::rollback();
throw $e;
}
}
/**
* 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, $claim_id, $encounter_id)
{
$request->validate([
'service_code' => 'required',
'tanggal_masuk' => 'required',
'tanggal_keluar' => 'required'
]);
// $claim = Claim::findOrFail($claim_id);
// return ($request->primary_diagnosis['id']);
// die;
// return $request->toArray();
try {
DB::beginTransaction();
$encounter = Encounter::findOrFail($encounter_id);
$encounterData = [
'status' => 'completed',
'class' => $request->service_code,
'type' => 'Consultation',
'start' => $request->tanggal_masuk,
'end' => $request->tanggal_keluar,
'number_of_bed' => $request->number_of_bed,
'duration_day' => $request->duration_day
];
if ($request->has('healthcare')) {
$encounterData['healthcare_id'] = $request->healthcare['id'];
}
// Update The Encounter
$encounter->fill($encounterData);
$encounter->save();
// --------------------------------------------
// Meta
// TODO Handle if healthcare not primaya
$encounter->metas()->updateOrCreate([
'type' => 'MEDRECID',
'system' => 'primaya-his'
], [
'type' => 'MEDRECID',
'system' => 'primaya-his',
'value' => $request->medical_record_number
]);
// ---------------------------------------------
// Handle Diagnosis
if ($request->has('primary_diagnosis')) {
$encounter->diagnoses()->where('type', 'primary')->delete();
$encounter->diagnoses()->create([
'diagnosis_id' => $request->primary_diagnosis['id'],
'type' => 'primary',
'use' => 'discharge',
'source' => 'primecenter',
'description' => 'Batching',
]);
}
if ($request->has('secondary_diagnoses')) {
$encounter->diagnoses()->where('type', 'secondary')->delete();
foreach ($request->secondary_diagnoses as $diagnosis) {
if (!isset($diagnosis['id'])) { // Handle Null Values
continue;
}
$encounter->diagnoses()->create([
'diagnosis_id' => $diagnosis['id'],
'type' => 'secondary',
'use' => 'discharge',
'source' => 'primecenter',
'description' => 'Batching',
]);
}
}
// ------------------------------------
// Handle Doctors as primary Doctor
// if ($request->has('doctor')) {
// $encounter->participants()->create([
// 'type' => 'Doctor',
// 'participantable_type' => Practitioner::class,
// 'participantable_id' => $request->doctor['id'],
// ]);
// }
DB::commit();
$encounter->load(['diagnoses', 'doctors', 'healthcare']);
return Helper::responseJson(data: EncounterResource::make($encounter), message: 'Encounter berhasil ditambahkan');
} catch (\Exception $e) {
DB::rollback();
throw $e;
}
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
public function setFinalEncounter(Request $request, $claim_id)
{
$request->validate([
'encounter_id' => 'required'
]);
$claim = Claim::findOrFail($claim_id);
$claim->final_encounter_id = $request->encounter_id;
$claim->save();
return Helper::responseJson(data: $claim, message: "Success Update Final Encounter");
}
}

View File

@@ -29,6 +29,7 @@ use Modules\Internal\Http\Controllers\Api\PlanController;
use Modules\Internal\Http\Controllers\Api\ProvinceController; use Modules\Internal\Http\Controllers\Api\ProvinceController;
use Modules\Internal\Http\Controllers\Api\SpecialityController; use Modules\Internal\Http\Controllers\Api\SpecialityController;
use Modules\Internal\Http\Controllers\Api\VillageController; use Modules\Internal\Http\Controllers\Api\VillageController;
use Modules\Internal\Http\Controllers\ClaimEncounterController;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -119,6 +120,10 @@ Route::prefix('internal')->group(function () {
Route::get('members', [MemberController::class, 'index']); Route::get('members', [MemberController::class, 'index']);
Route::get('members/{member_id}/benefits', [MemberController::class, 'benefits']); Route::get('members/{member_id}/benefits', [MemberController::class, 'benefits']);
Route::post('claims/{claim_id}/encounters', [ClaimEncounterController::class, 'store']);
Route::post('claims/{claim_id}/encounters/{encounter_id}/update', [ClaimEncounterController::class, 'update']);
Route::post('claims/{claim_id}/set-final-encounter', [ClaimEncounterController::class, 'setFinalEncounter']);
Route::get('claims', [ClaimController::class, 'index']); Route::get('claims', [ClaimController::class, 'index']);
Route::post('claims/{id}/update-items', [ClaimController::class, 'updateItems'])->name('claim.update-items'); Route::post('claims/{id}/update-items', [ClaimController::class, 'updateItems'])->name('claim.update-items');
Route::post('claims/{id}/update-diagnosis', [ClaimController::class, 'updateDiagnosis'])->name('claim.update-diagnosis'); Route::post('claims/{id}/update-diagnosis', [ClaimController::class, 'updateDiagnosis'])->name('claim.update-diagnosis');

View File

@@ -37,6 +37,10 @@ class ClaimShowResource extends JsonResource
$data['primary_diagnosis'] = $this->diagnoses->filter(function($diagnosis){ return $diagnosis->type == 'primary';})->values(); $data['primary_diagnosis'] = $this->diagnoses->filter(function($diagnosis){ return $diagnosis->type == 'primary';})->values();
$data['secondary_diagnosis'] = $this->diagnoses->filter(function($diagnosis){ return $diagnosis->type == 'secondary';})->values(); $data['secondary_diagnosis'] = $this->diagnoses->filter(function($diagnosis){ return $diagnosis->type == 'secondary';})->values();
$data['encounters'] = $this->encounters->map(function($encounter) {
$encounterData = EncounterResource::make($encounter);
return $encounterData;
});
return $data; return $data;
} }
} }

View File

@@ -0,0 +1,31 @@
<?php
namespace Modules\Internal\Transformers;
use Illuminate\Http\Resources\Json\JsonResource;
class EncounterResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
$encounter = parent::toArray($request);
$encounter['class_name'] = $this->service->name;
$encounter['primary_diagnosis'] = $this->primaryDiagnoses->first();
$encounter['primary_doctor'] = $this->doctors->first()
? array_merge(
$this->doctors->first()->person->toArray(),
$this->doctors->first()->toArray()
)
: null;
$encounter['medical_record_number'] = @$this->meta->MEDRECID ?? null;
return $encounter;
}
}

View File

@@ -149,6 +149,16 @@ class Claim extends Model
return $this->belongsTo(Member::class, 'member_id'); return $this->belongsTo(Member::class, 'member_id');
} }
public function encounters()
{
return $this->belongsToMany(Encounter::class, 'claim_encounter');
}
public function finalEncounter()
{
return $this->belongsTo(Encounter::class, 'final_encounter_id');
}
public function diagnoses() public function diagnoses()
{ {
return $this->hasMany(ClaimDiagnosis::class, 'claim_id'); return $this->hasMany(ClaimDiagnosis::class, 'claim_id');
@@ -185,5 +195,10 @@ class Claim extends Model
->whereIn('status', ['approved', 'paid']); ->whereIn('status', ['approved', 'paid']);
// ->whereBetween('requested_at', [$startDate, $endDate]); // ->whereBetween('requested_at', [$startDate, $endDate]);
} }
public function getTotalTagihanAttribute()
{
return $this->items->sum('nominal_ditagihkan');
}
} }

View File

@@ -28,6 +28,11 @@ class ClaimDiagnosis extends Model
'deleted_by', 'deleted_by',
]; ];
public $type_enums = [
'primary' => 'Primary',
'secondary' => 'Secondary'
];
public function icd() public function icd()
{ {
return $this->belongsTo(Icd::class, 'diagnosis_id', 'id'); return $this->belongsTo(Icd::class, 'diagnosis_id', 'id');

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
class Encounter extends Model class Encounter extends Model
{ {
@@ -15,8 +16,37 @@ class Encounter extends Model
'class', 'class',
'type', 'type',
'patient_id', 'patient_id',
'healthcare_id',
'start', 'start',
'end', 'end',
'number_of_bed',
'duration_day'
];
public $casts = [
'start' => 'datetime',
'end' => 'datetime'
];
public static $status_enums = [
'completed' => 'Completed',
'planned' => 'Planned',
'in-progress' => 'In Progress',
'on-hold' => 'On Hold',
'discharged' => 'Discharged',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
'discontinued' => 'Discontinued',
'entered-in-error' => 'Entered in Error',
'unknown' => 'Unknown',
];
public $with = [
'metas'
];
public $append = [
'meta'
]; ];
public function participants() public function participants()
@@ -24,8 +54,55 @@ class Encounter extends Model
return $this->hasMany(EncounterParticipant::class, 'encounter_id'); return $this->hasMany(EncounterParticipant::class, 'encounter_id');
} }
public function doctors()
{
// return $this->hasManyThrough(CorporateService::class, Service::class, 'corporate_id', 'service_code', 'id', 'service_code');
return $this->hasManyThrough(Practitioner::class, EncounterParticipant::class, 'encounter_id', 'id', 'id', 'participantable_id')
->where(
'participantable_type',
array_search(static::class, Relation::morphMap()) ?: Practitioner::class
);
}
public function diagnoses() public function diagnoses()
{ {
return $this->hasMany(EncounterDiagnosis::class, 'encounter_id'); return $this->hasMany(EncounterDiagnosis::class, 'encounter_id');
} }
public function healthcare()
{
return $this->belongsTo(Organization::class, 'healthcare_id');
}
public function metas()
{
return $this->morphMany(Meta::class, 'metaable');
}
public function primaryDiagnoses()
{
return $this->diagnoses()->where('type', 'primary');
}
public function secondaryDiagnoses()
{
return $this->diagnoses()->wher('type', 'primary');
}
public function service()
{
return $this->belongsTo(Service::class, 'class', 'code');
}
public function getMetaAttribute()
{
$orgMeta = [];
foreach ($this->metas as $meta) {
$orgMeta[$meta->type] = $meta->value;
}
return (object) $orgMeta;
}
} }

View File

@@ -12,6 +12,7 @@ class EncounterDiagnosis extends Model
public $fillable = [ public $fillable = [
'encounter_id', 'encounter_id',
'diagnosis_id', 'diagnosis_id',
'type',
'use', 'use',
'source', 'source',
'description', 'description',
@@ -22,8 +23,18 @@ class EncounterDiagnosis extends Model
'final' => 'Final' 'final' => 'Final'
]; ];
public static $type_enums = [
'primary' => 'Primary',
'secondary' => 'Secondary'
];
public function encounter() public function encounter()
{ {
return $this->belongsTo(Encounter::class, 'encounter_id'); return $this->belongsTo(Encounter::class, 'encounter_id');
} }
public function diagnosis()
{
return $this->belongsTo(Icd::class, 'diagnosis_id');
}
} }

View File

@@ -12,6 +12,8 @@ class EncounterParticipant extends Model
public $fillable = [ public $fillable = [
'encounter_id', 'encounter_id',
'type', 'type',
'participantable_type',
'participantable_id'
]; ];

View File

@@ -15,13 +15,16 @@ return new class extends Migration
{ {
Schema::create('encounters', function (Blueprint $table) { Schema::create('encounters', function (Blueprint $table) {
$table->id(); $table->id();
$table->foreignId('part_of')->comment('encounter_id'); $table->foreignId('part_of')->nullable()->comment('encounter_id')->index();
$table->string('status'); $table->string('status');
$table->string('class')->nullable(); $table->string('class')->nullable();
$table->string('type')->nullable(); $table->string('type')->nullable();
$table->foreignId('patient_id')->nullable(); $table->foreignId('patient_id')->nullable()->index();
$table->foreignId('healthcare_id')->nullable()->index();
$table->dateTime('start')->nullable(); $table->dateTime('start')->nullable();
$table->dateTime('end')->nullable(); $table->dateTime('end')->nullable();
$table->integer('number_of_bed')->nullable();
$table->integer('duration_day')->nullable();
$table->timestamps(); $table->timestamps();
$table->softDeletes(); $table->softDeletes();

View File

@@ -17,6 +17,7 @@ return new class extends Migration
$table->id(); $table->id();
$table->foreignId('encounter_id'); $table->foreignId('encounter_id');
$table->foreignId('diagnosis_id'); $table->foreignId('diagnosis_id');
$table->string('type')->nullable();
$table->string('use')->nullable(); $table->string('use')->nullable();
$table->text('source')->nullable(); $table->text('source')->nullable();
$table->text('description')->nullable(); $table->text('description')->nullable();

View File

@@ -0,0 +1,34 @@
<?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('claim_encounter', function (Blueprint $table) {
$table->id();
$table->foreignId('claim_id');
$table->foreignId('encounter_id');
$table->index(['claim_id', 'encounter_id'], 'claim_encounter_index');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('claim_encounter');
}
};

View File

@@ -0,0 +1,32 @@
<?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::table('claims', function (Blueprint $table) {
$table->foreignId('final_encounter_id')->nullable()->after('benefit_id')->index();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('claims', function (Blueprint $table) {
$table->dropColumn('final_encounter_id');
});
}
};

View File

@@ -17,6 +17,7 @@ export default function AutocompleteDoctor({
currentValue, currentValue,
textLabel, textLabel,
currentOptions = [], currentOptions = [],
...other
}: autocompleteDoctorType) { }: autocompleteDoctorType) {
const [options, setOptions] = useState<IcdType>(currentOptions); const [options, setOptions] = useState<IcdType>(currentOptions);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -56,7 +57,7 @@ export default function AutocompleteDoctor({
}} }}
loading={loading} loading={loading}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/> <TextField {...params} {...other} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
)} )}
/> />
); );

View File

@@ -17,6 +17,7 @@ export default function AutocompleteHealthcare({
currentValue, currentValue,
textLabel, textLabel,
currentOptions = [], currentOptions = [],
...other
}: autocompleteHealthcareType) { }: autocompleteHealthcareType) {
const [options, setOptions] = useState<IcdType>(currentOptions); const [options, setOptions] = useState<IcdType>(currentOptions);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -56,7 +57,7 @@ export default function AutocompleteHealthcare({
}} }}
loading={loading} loading={loading}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/> <TextField {...params} {...other} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
)} )}
/> />
); );

View File

@@ -49,13 +49,30 @@ export default function RHFDatepicker({ name, ...other }: IProps & TextFieldProp
/> />
)} )}
/> */} /> */}
<DesktopDatePicker {/* <DesktopDatePicker
value={field.value} value={field.value}
inputFormat="dd/MM/yyyy" inputFormat="dd/MM/yyyy"
onChange={(value) => { onChange={(value) => {
field.onChange(value) field.onChange(value)
}} }}
renderInput={(params) => <TextField {...params} fullWidth />} renderInput={(params) => <TextField {...params} fullWidth />}
/> */}
<DesktopDatePicker
value={field.value}
inputFormat="dd/MM/yyyy"
onChange={(value) => {
field.onChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!error}
helperText={error?.message}
{...other}
/>
)}
/> />
</LocalizationProvider> </LocalizationProvider>
)} )}

View File

@@ -19,30 +19,25 @@ import {
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import { Controller, useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router-dom';
import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs'; import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form';
import Page from '../../components/Page'; import Page from '../../components/Page';
import useSettings from '../../hooks/useSettings'; import useSettings from '../../hooks/useSettings';
import { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import axios from '../../utils/axios'; import axios from '../../utils/axios';
import { enqueueSnackbar } from 'notistack'; import { enqueueSnackbar } from 'notistack';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber'; import { fDate } from '@/utils/formatTime';
import Iconify from '../../components/Iconify'; import Iconify from '../../components/Iconify';
import Form from './Form';
import Documents from './components/Documents'; import Documents from './components/Documents';
import DiagnosisHistory from './components/DiagnosisHistory'; import DiagnosisHistory from './components/DiagnosisHistory';
import ClaimItems from './components/ClaimItems'; import ClaimItems from './components/ClaimItems';
import DialogMemberBenefit from './components/DialogMemberBenefit'; import DialogMemberBenefit from './components/DialogMemberBenefit';
import AutocompleteDiagnosis from '@/components/autocomplete/AutocompleteDiagnosis';
import AutocompleteDiagnosisControlled from '@/components/autocomplete/AutocompleteDiagnosisControlled';
import { Icd, IcdType } from '@/@types/diagnosis';
import { OrganizationType, PractitionerType } from '@/@types/doctor';
import ClaimDetail from './components/ClaimDetail'; import ClaimDetail from './components/ClaimDetail';
import DialogHistoryPerawatan from './components/DialogHistoryPerawatan';
import { IconButton } from '@mui/material';
import { Tooltip } from '@mui/material';
export default function ClaimsCreateUpdate() { export default function ClaimsCreateUpdate() {
const { themeStretch } = useSettings(); const { themeStretch } = useSettings();
@@ -198,8 +193,9 @@ export default function ClaimsCreateUpdate() {
}); });
}; };
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// History Perawatan
const [openDialogHistoryPerawatan, setOpenDialogHistoryPerawatan] = useState(false);
useEffect(() => { useEffect(() => {
axios.get('/claims/' + id).then(({ data }) => { axios.get('/claims/' + id).then(({ data }) => {
@@ -212,6 +208,33 @@ export default function ClaimsCreateUpdate() {
}); });
}, [id]); }, [id]);
const handleSetFinalEncounter = (encounter) => {
const tempLastFinalEncounterId = currentClaim.final_encounter_id
axios.post(`/claims/${id}/set-final-encounter`, {
encounter_id: encounter.id
})
.then((res) => {
setCurrentClaim({...currentClaim, ...{final_encounter_id: encounter.id}})
enqueueSnackbar(res.data.message, {variant: 'success'})
})
.catch((err) => {
setCurrentClaim({...currentClaim, ...{final_encounter_id: tempLastFinalEncounterId}})
enqueueSnackbar(err.message, {variant: 'error'})
})
setCurrentClaim({...currentClaim, ...{final_encounter_id: encounter.id}})
}
// ------------------------------------------------------------------
// Edit History Perawatan
const [openDialogEditHistoryPerawatan, setOpenDialogEditHistoryPerawatan] = useState(false)
const [editedEncounter, setEditedEncounter] = useState(null)
const showEditEncounter = (encounter) => {
setEditedEncounter(encounter)
setOpenDialogEditHistoryPerawatan(true)
}
return ( return (
<Page title={`Claim : ${currentClaim?.code}`}> <Page title={`Claim : ${currentClaim?.code}`}>
<Container maxWidth={themeStretch ? false : 'xl'}> <Container maxWidth={themeStretch ? false : 'xl'}>
@@ -359,9 +382,86 @@ export default function ClaimsCreateUpdate() {
</Grid> </Grid>
<Grid item xs={7}> <Grid item xs={7}>
{/* Diagnosis */} {/* Claim Detail */}
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2 }}> <Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2 }}>
<ClaimDetail claim={currentClaim} /> <Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{ marginBottom: 2 }}
>
<Stack direction="row" alignItems="center" spacing={1}>
<Iconify icon="eva:bell-fill" />
<Typography variant="body2" fontWeight={600}>
History Perawatan
</Typography>
</Stack>
<Typography
onClick={() => {
setOpenDialogHistoryPerawatan(true);
}}
>
+ Tambah History Perawatan
</Typography>
</Stack>
{/* For Creation History Perawatan / Encounter */}
<DialogHistoryPerawatan
openDialog={openDialogHistoryPerawatan}
setOpenDialog={setOpenDialogHistoryPerawatan}
onChange={() => {}}
claim={currentClaim}
></DialogHistoryPerawatan>
<Paper sx={{ background: 'white', marginY: 2, p: 2 }}>
<Stack sx={{ maxHeight: '250px', overflowY: 'scroll' }}>
{currentClaim?.encounters && currentClaim?.encounters.map((encounter, index) => (
<React.Fragment key={index}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{ marginY: 1 }}
>
<Stack>
<Typography fontWeight={'bold'} onClick={() => {showEditEncounter(encounter)}}><a href="#">{ encounter.class_name }</a></Typography>
<Typography sx={{ color: '#777', fontSize: '10px' }}>
({ fDate(encounter.start) } - { fDate(encounter.end) })
</Typography>
<Typography variant="body2">Diagnosis: { encounter.primary_diagnosis ? (`(${encounter.primary_diagnosis.diagnosis?.code}) ${encounter.primary_diagnosis.diagnosis?.name}`) : '-'}</Typography>
<Typography variant="body2">Dokter: { encounter.primary_doctor ? (`${encounter.primary_doctor.name}`) : '-'}</Typography>
</Stack>
<Tooltip title="Diagnosa Final">
<IconButton onClick={() => {handleSetFinalEncounter(encounter)}}>
<Iconify
icon="eva:checkmark-circle-2-fill"
color={currentClaim.final_encounter_id == encounter.id ? "green" : "gray"}
sx={{ margin: 2 }}
></Iconify>
</IconButton>
</Tooltip>
</Stack>
<Divider />
</React.Fragment>
))}
{(!currentClaim?.encounters || currentClaim?.encounters.length == 0) && (
<Typography>Belum ada History Perawatan</Typography>
)}
</Stack>
</Paper>
{/* For Editing History Perawatan / Encounter */}
<DialogHistoryPerawatan
openDialog={openDialogEditHistoryPerawatan}
setOpenDialog={setOpenDialogEditHistoryPerawatan}
onChange={() => {}}
claim={currentClaim}
encounter={editedEncounter}
></DialogHistoryPerawatan>
{/* <ClaimDetail claim={currentClaim} /> */}
</Paper> </Paper>
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}> <Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>

View File

@@ -21,19 +21,19 @@ export default function DiagnosisHistory({ diagnosis }) {
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}> <Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center"> <Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="row" alignItems="center" spacing={1}> <Stack direction="row" alignItems="center" spacing={1}>
<Iconify icon="eva:bell-fill" /> <Iconify icon="eva:clock-outline" />
<Typography variant="body2" fontWeight={600}> <Typography variant="body2" fontWeight={600}>
Riwayat Diagnosa Riwayat Diagnosa
</Typography> </Typography>
</Stack> </Stack>
<Typography {/* <Typography
variant="body2" variant="body2"
onClick={() => { onClick={() => {
setOpenDialogRequestDocument(true); setOpenDialogRequestDocument(true);
}} }}
> >
View All View All
</Typography> </Typography> */}
</Stack> </Stack>
<Paper sx={{ background: 'white', marginTop: 2 }}> <Paper sx={{ background: 'white', marginTop: 2 }}>

View File

@@ -0,0 +1,73 @@
import MuiDialog from '@/components/MuiDialog';
import axios from '@/utils/axios';
import { Button, Checkbox, Typography } from '@mui/material';
import { Paper } from '@mui/material';
import { Stack } from '@mui/material';
import { enqueueSnackbar } from 'notistack';
import { useState } from 'react';
import FormHistoryPerawatan from './FormHistoryPerawatan';
type DialogHistoryPerawatanType = {
openDialog: boolean;
setOpenDialog: void;
onSubmit?: void;
claim: any; // TODO create ClaimType
encounter?: any;
}
export default function DialogHistoryPerawatan({ openDialog, setOpenDialog, onSubmit, claim, encounter } : DialogHistoryPerawatanType) {
const isEdit = encounter?.id != null
// const benefits = member?.current_plan?.benefits ?? [];
// const [selectedBenefits, setSelectedBenefits] = useState([]);
// const toggleBenefit = (benefit) => {
// if (selectedBenefits.includes(benefit)) {
// console.log('removing', benefit)
// setSelectedBenefits(selectedBenefits.filter((throughBenefit) => benefit.id != throughBenefit.id))
// } else {
// console.log('adding', benefit)
// setSelectedBenefits([...selectedBenefits, benefit])
// }
// }
const handleSubmit = (data) => {
if (!isEdit) {
axios.post(`claims/${claim.id}/encounters`, data)
.then((res) => {
enqueueSnackbar(res.data.message, {variant: 'success'})
setOpenDialog(false);
})
.catch((err) => {
enqueueSnackbar(err.message, {variant: 'error'})
})
} else {
axios.post(`claims/${claim.id}/encounters/${data.id}/update`, data)
.then((res) => {
enqueueSnackbar(res.data.message, {variant: 'success'})
setOpenDialog(false);
})
.catch((err) => {
enqueueSnackbar(err.message, {variant: 'error'})
})
}
};
const getContent = () => (
<Stack spacing={1} marginTop={2}>
<FormHistoryPerawatan claim={claim} onSubmit={handleSubmit} encounter={encounter}></FormHistoryPerawatan>
</Stack>
);
return (
<MuiDialog
title={{ name: !isEdit ? 'Tambah History Perawatan' : 'Edit History Perawatan' }}
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="xl"
/>
);
}

View File

@@ -0,0 +1,352 @@
import * as Yup from 'yup';
import { IcdType } from '@/@types/diagnosis';
import { OrganizationType, PractitionerType } from '@/@types/doctor';
import AutocompleteDiagnosisControlled from '@/components/autocomplete/AutocompleteDiagnosisControlled';
import { LoadingButton } from '@mui/lab';
import { Paper, Stack, TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import React, { useEffect, useMemo, useState } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { enqueueSnackbar } from 'notistack';
import axios from '@/utils/axios';
import { FormProvider, RHFDatepicker, RHFTextField, RHFSelect } from '@/components/hook-form';
import AutocompleteDoctor from '@/components/autocomplete/AutocompleteDoctor';
import AutocompleteHealthcare from '@/components/autocomplete/AutocompleteHealthcare';
import { Typography } from '@mui/material';
import Iconify from '@/components/Iconify';
import { Divider } from '@mui/material';
type FormHistoryPerawatanProps = {
claim: any;
onSubmit: void;
encounter?: any; // TODO EncounterType
};
export default function FormHistoryPerawatan({
claim,
onSubmit,
encounter,
}: FormHistoryPerawatanProps) {
const isEdit = encounter?.id != null;
// --------------------------------------------------------------
// Diagnosis
const [primaryDiagnosis, setPrimaryDiagnosis] = useState(null);
const [secondaryDiagnosis, setSecondaryDiagnosis] = useState(null);
const [loadingDiagnosis, setLoadingDiagnosis] = useState(false);
const [primaryDiagnosisOptions, setPrimaryDiagnosisOptions] = useState([]);
const [secondaryDiagnosisOptions, setSecondaryDiagnosisOptions] = useState([]);
const [doctorOptions, setDoctorOptions] = useState([]);
const [healthcareOptions, setHealthcareOptions] = useState([]);
const ClaimDetailSchema = Yup.object().shape({
primary_diagnosis: Yup.object().required('Diagnosis Utama Wajib dipilih'),
// secondary_diagnosis: Yup.object().required('Diagnosis Utama Wajib dipilih'),
doctor: Yup.object().required('Dokter Harus dipilih'),
healthcare: Yup.object().required('Healthcare Harus dipilih'),
});
interface FormValuesProps extends Partial<any> {
service_code: String;
primary_diagnosis: IcdType;
secondary_diagnosis: IcdType;
doctor: PractitionerType;
healthcare: OrganizationType;
secondary_diagnoses: IcdType[];
}
const defaultValues = useMemo(
() => ({
service_code: 'OP',
tanggal_masuk: null,
tanggal_keluar: null,
primary_diagnosis: null,
doctor: null,
healthcare: null,
no_medrec: '',
bed: '',
duration: '',
secondary_diagnoses: [],
}),
[claim]
);
const methods = useForm<FormValuesProps>({
resolver: yupResolver(ClaimDetailSchema),
defaultValues,
});
const {
reset,
watch,
control,
setValue,
getValues,
setError,
handleSubmit,
formState: { isSubmitting },
} = methods;
const values = watch();
const handleSaveDiagnosis = () => {
setLoadingDiagnosis(true);
axios
.post(`claims/${claim.id}/update-diagnosis`, {
primary: [getValues('primary_diagnosis')?.id],
secondary: [getValues('secondary_diagnosis')?.id],
})
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
})
.catch((err) => {
setLoadingDiagnosis(false);
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
setLoadingDiagnosis(false);
});
};
useEffect(() => {
if (claim) {
// SET Default Primary Diagnosis
// if (claim.primary_diagnosis.length && claim.primary_diagnosis[0].icd) {
// setPrimaryDiagnosisOptions(
// claim.primary_diagnosis.map((diagnosis) => {
// if (diagnosis.icd) {
// return diagnosis.icd;
// }
// })
// );
// setValue('primary_diagnosis', claim.primary_diagnosis[0].icd);
// }
// SET Default Secondary Diagnosis
// if (claim.secondary_diagnosis.length && claim.secondary_diagnosis[0].icd) {
// setSecondaryDiagnosisOptions(
// claim.secondary_diagnosis.map((diagnosis) => {
// if (diagnosis.icd) {
// return diagnosis.icd;
// }
// })
// );
// setValue('secondary_diagnosis', claim.secondary_diagnosis[0].icd);
// }
}
}, [claim]);
useEffect(() => {
if (encounter?.id) {
// Primary Diagnosis
if (encounter.primary_diagnosis && encounter.primary_diagnosis?.diagnosis) {
setPrimaryDiagnosisOptions([encounter.primary_diagnosis.diagnosis]);
setValue('primary_diagnosis', encounter.primary_diagnosis.diagnosis);
}
// Sondary Diagnoses
if (encounter.secondary_diagnoses && encounter.secondary_diagnoses.length) {
encounter.secondary_diagnoses.map();
setPrimaryDiagnosisOptions([encounter.primary_diagnosis.diagnosis]);
setValue('primary_diagnosis', encounter.primary_diagnosis.diagnosis);
}
if (encounter.primary_doctor) {
// TODO Clear Up Data to used by autocomplete
setDoctorOptions([encounter.primary_doctor]);
setValue('doctor', encounter.primary_doctor);
}
if (encounter.healthcare) {
// TODO Clear Up Data to used by autocomplete
setDoctorOptions([encounter.healthcare]);
setValue('healthcare', encounter.healthcare);
}
setValue('service_code', encounter.class);
setValue('tanggal_masuk', encounter.start);
setValue('tanggal_keluar', encounter.end);
setValue('medical_record_number', encounter.medical_record_number);
setValue('number_of_bed', encounter.number_of_bed);
setValue('duration_day', encounter.duration_day);
setValue('secondary_diagnoses', encounter.secondary_diagnosis);
setValue('id', encounter.id);
}
}, [encounter]);
function handleAddSecondaryDiagnosis() {
setValue('secondary_diagnoses', [...getValues('secondary_diagnoses'), null]);
}
function handleRemoveSecondaryDiagnosis(diagnosis) {
// TODO Fix Bug
const newDiagnoses = getValues('secondary_diagnoses').filter((val) => {
return diagnosis !== val;
});
setValue('secondary_diagnoses', newDiagnoses);
}
const services = [
{
code: 'OP',
name: 'Rawat Jalan',
},
{
code: 'IP',
name: 'Rawat Inap',
},
];
const submitting = () => {
onSubmit(getValues());
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(submitting)}>
<Stack spacing={2}>
<Stack spacing={2} sx={{ background: 'white', p: 2 }}>
<RHFSelect name="service_code" label="Layanan" placeholder="Layanan">
{/* <option value="" /> */}
{services.map((option, index) => (
<option key={index} value={option.code}>
{option.name}
</option>
))}
</RHFSelect>
<RHFDatepicker
name="tanggal_masuk"
placeholder="DD/MM/YYYY"
label="Tanggal Masuk"
></RHFDatepicker>
<RHFDatepicker
name="tanggal_keluar"
placeholder="DD/MM/YYYY"
label="Tanggal Keluar"
></RHFDatepicker>
<Controller
name="healthcare"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteHealthcare
onChange={onChange}
currentOptions={healthcareOptions}
currentValue={value}
textLabel="Tempat Dirawat"
placeholder="Ketik nama fasilitas kesehatan"
/>
)}
/>
<Controller
name="doctor"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteDoctor
onChange={onChange}
currentOptions={doctorOptions}
currentValue={value}
textLabel="Dokter yang merawat"
placeholder="Ketik nama dokter"
/>
)}
/>
<RHFTextField
name="medical_record_number"
placeholder='"No. Rekam Medis" di fasilitas kesehatan'
label="No. Rekam Medis"
></RHFTextField>
<RHFTextField name="number_of_bed" label="Jumlah Tempat Tidur"></RHFTextField>
<RHFTextField name="duration_day" label="Lama Perawatan (hari)"></RHFTextField>
</Stack>
<Paper variant="outlined" sx={{ background: 'white', p: 2 }}>
<Stack spacing={2}>
{/* {JSON.stringify(getValues('primary_diagnosis'))} */}
<Controller
name="primary_diagnosis"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteDiagnosisControlled
onChange={onChange}
currentOptions={primaryDiagnosisOptions}
currentValue={value}
textLabel="Diagnosis Utama (ICD-X)"
/>
)}
/>
{/* SECONDARY DIAGNOSES */}
<Stack direction="row" justifyContent="end">
<Typography
onClick={() => {
handleAddSecondaryDiagnosis();
}}
>
+ Secondary Diagnosis
</Typography>
</Stack>
{getValues('secondary_diagnoses') &&
getValues('secondary_diagnoses').map(function (diagnosis, index) {
return (
<React.Fragment key={index}>
{/* <Stack direction="row" sx={{ width: '100%'}}> */}
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
divider={<Divider orientation="horizontal" flexItem />}
>
<Typography>
<strong>#{index + 1}</strong>
</Typography>
<Iconify
icon="eva-trash-outline"
color="red"
onClick={() => {
handleRemoveSecondaryDiagnosis(diagnosis);
}}
></Iconify>
</Stack>
<Controller
name={`secondary_diagnoses[${index}]`}
control={control}
sx={{ width: '100%' }}
render={({ field: { onChange, value } }) => (
<AutocompleteDiagnosisControlled
onChange={onChange}
currentOptions={[diagnosis]}
currentValue={value}
textLabel="Diagnosis Tambahan (ICD-X)"
/>
)}
/>
{/* </Stack> */}
</React.Fragment>
);
})}
</Stack>
</Paper>
{(claim?.status == 'requested' || claim?.status == 'received') && (
<LoadingButton
variant="contained"
sx={{ marginTop: 2 }}
loading={loadingDiagnosis}
onClick={() => {
submitting();
}}
>
Simpan Detail
</LoadingButton>
)}
</Stack>
</FormProvider>
);
}

View File

@@ -184,35 +184,35 @@ use App\Helpers\Helper;
<tr><td colspan="99" style="background: red; padding-left: 30px; color: white">B. Informasi Perawatan</td></tr> <tr><td colspan="99" style="background: red; padding-left: 30px; color: white">B. Informasi Perawatan</td></tr>
<tr> <tr>
<td style="width: 25%">1. Tanggal Masuk</td> <td style="width: 25%">1. Tanggal Masuk</td>
<td style="width: 25%">: {{ '' }}</td> <td style="width: 25%">: {{ $claim->finalEncounter->start ?? "" }}</td>
<td style="width: 25%">7. Lama Perawatan</td> <td style="width: 25%">7. Lama Perawatan</td>
<td style="width: 25%">: {{ '' }}</td> <td style="width: 25%">: {{ $claim->finalEncounter->duration_day ?? "" }}</td>
</tr> </tr>
<tr> <tr>
<td>2. Tanggal Keluar</td> <td>2. Tanggal Keluar</td>
<td>: {{ '' }}</td> <td>: {{ $claim->finalEncounter->end }}</td>
<td>8. Kamar Perawatan</td> <td>8. Kamar Perawatan</td>
<td>: </td> <td>: {{ "" }}</td>
</tr> </tr>
<tr> <tr>
<td>3. Nama Rumah Sakit</td> <td>3. Nama Rumah Sakit</td>
<td>: {{ $hospital->name }}</td> <td>: {{ $hospital->name }}</td>
<td>9. Jumlah Tempat Tidur</td> <td>9. Jumlah Tempat Tidur</td>
<td>: {{ '' }}</td> <td>: {{ $claim->finalEncounter->number_of_bed }}</td>
</tr> </tr>
<tr> <tr>
<td>4. Dokter yang Merawat</td> <td>4. Dokter yang Merawat</td>
<td>: {{ '' }}</td> <td>: {{ $claim->finalEncounter->doctors->first()->person->name ?? '' }}</td>
<td>10. Estimasi Biaya Rawat Inap</td> <td>10. Estimasi Biaya Rawat Inap</td>
<td>: {{ '' }}</td> <td>: {{ $claim->total_tagihan ? Helper::currencyIdrFormat($claim->total_tagihan) : "" }}</td>
</tr> </tr>
<tr> <tr>
<td>5. No. Rekam Medis</td> <td>5. No. Rekam Medis</td>
<td>: {{ '' }}</td> <td>: {{ $claim->finalEncounter->meta->MEDRECID }}</td>
<td>11. Diagnosa</td> <td>11. Diagnosa</td>
<td>: {{ $claim->diagnosis?->icd?->code ?? '' }}/{{ $claim->diagnosis?->icd?->name ?? '' }}</td> <td>: {{ $claim->diagnosis?->icd?->code ?? '' }}/{{ $claim->diagnosis?->icd?->name ?? '' }}</td>