diff --git a/Modules/Client/Transformers/MemberResources.php b/Modules/Client/Transformers/MemberResources.php index b9efac92..9cfa741d 100644 --- a/Modules/Client/Transformers/MemberResources.php +++ b/Modules/Client/Transformers/MemberResources.php @@ -19,7 +19,7 @@ class MemberResources extends JsonResource 'memberId' => $this->member_id, 'fullName' => $this->full_name, $this->mergeWhen($request->input('claimMember') === 'false', [ - 'division' => $this->division->name, + 'division' => $this->division->name ?? '', 'status' => $this->active ]), 'limit' => [ diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php index f4bfa352..2abb36d2 100644 --- a/Modules/Internal/Http/Controllers/Api/ClaimController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -6,10 +6,10 @@ use App\Models\Benefit; use App\Models\Claim; use App\Models\Icd; use App\Models\Member; +use App\Services\ClaimService; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Modules\Internal\Services\ClaimService; class ClaimController extends Controller { @@ -84,7 +84,13 @@ class ClaimController extends Controller public function show($id) { $claim = Claim::query() - ->with(['member', 'member.currentPlan']) + ->with([ + 'member', + 'member.currentPlan', + 'diagnosis', + 'benefit', + 'files' + ]) ->findOrFail($id); return response()->json($claim); diff --git a/Modules/Internal/Services/ClaimService.php b/Modules/Internal/Services/ClaimService.php index aba0eacb..ec1429ae 100644 --- a/Modules/Internal/Services/ClaimService.php +++ b/Modules/Internal/Services/ClaimService.php @@ -7,114 +7,5 @@ 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, $status) - { - try { - DB::beginTransaction(); - - $claimData = [ - 'member_id' => $member->id, - 'diagnosis_id' => $diagnosis->id, - 'total_claim' => $totalClaim, - 'currency' => 'IDR', - 'plan_id' => $member->currentPlan->id, - 'benefit_id' => $benefit->id, - 'status' => $status - ]; - $claimData[$status.'_at'] = now(); - $claimData[$status.'_by'] = auth()->user()->id ?? null; - - $claim = Claim::create($claimData); - - $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(); - - throw new \Exception($error); - } - } } \ No newline at end of file diff --git a/Modules/Internal/Transformers/ClaimResource.php b/Modules/Internal/Transformers/ClaimResource.php new file mode 100644 index 00000000..edac6ff4 --- /dev/null +++ b/Modules/Internal/Transformers/ClaimResource.php @@ -0,0 +1,22 @@ +files->groupBy('type'); + + return $claimData; + } +} diff --git a/app/Http/Controllers/Api/MembershipController.php b/app/Http/Controllers/Api/MembershipController.php new file mode 100644 index 00000000..23232ca0 --- /dev/null +++ b/app/Http/Controllers/Api/MembershipController.php @@ -0,0 +1,58 @@ +validate([ + 'member_id' => 'required', + 'birth_date' => 'required', + ]); + + $member = Member::where('member_id', $request->member_id)->first(); + + if (!$member) { + return Helper::responseJson(statusCode: 404, message: 'Member not found.', status: 'error'); + } + + if (!$member->active) { + return Helper::responseJson(statusCode: 406, message: 'The Member '.$request->member_id.' is Inactive.', status: 'error'); + } + + + return Helper::responseJson(data: $member, message: 'Member Found'); + } + + public function checkLimit(Request $request) + { + $request->validate([ + 'member_id' => 'required', + 'type' => 'required|in:consultation-gp,consultation-specialist,medicine' + ]); + + if ($request->type == 'consultation-gp') { + $benefitCode = 'OPCONS1'; + } + if ($request->type == 'consultation-specialist') { + $benefitCode = 'OPCONS2'; + } + if ($request->type == 'medicine') { + $benefitCode = 'OPMEDI1'; + } + + $member = Member::where('member_id', $request->member_id)->with(['currentCorporate', 'currentPolicy', 'currentPlan'])->first(); + + $limits = ClaimService::showMemberBenefitLimit($member, $benefitCode); + + return Helper::responseJson(data: $limits); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index bf500999..20b3f86e 100755 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -64,5 +64,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'linksehat.old.auth' => \App\Http\Middleware\LinksehatOldAuthMiddleware::class, ]; } diff --git a/app/Http/Middleware/LinksehatOldAuthMiddleware.php b/app/Http/Middleware/LinksehatOldAuthMiddleware.php new file mode 100644 index 00000000..03b8f3d7 --- /dev/null +++ b/app/Http/Middleware/LinksehatOldAuthMiddleware.php @@ -0,0 +1,25 @@ +header('authorization') == 'Bearer LpMbGm0NQvFC3lUBiy1Ch3NzS0CIPSmanR12FcdP') { + return $next($request); + } + + return abort(401, "Unauthenticated"); + } +} diff --git a/app/Models/Benefit.php b/app/Models/Benefit.php index 9c789ff9..9e8d80d0 100755 --- a/app/Models/Benefit.php +++ b/app/Models/Benefit.php @@ -132,6 +132,21 @@ class Benefit extends Model "Show Benefit Value" => 'show_benefit_value', ]; + public static $max_frequency_periods = [ + 0 => 'Policy Period', + 1 => 'Daily Visit', + 2 => 'Weekly', + 3 => 'Monthly', + 4 => 'Yearly', + 5 => 'Disability', + 6 => 'Visit', + ]; + + protected $appends = [ + 'max_frequency_period_name', + 'max_frequency' + ]; + public function setAreaLimitAttribute($value) { $this->attributes['area_limit'] = empty($value) ? null : $value; @@ -168,4 +183,42 @@ class Benefit extends Model { return $this->belongsTo(Plan::class, 'plan_code', 'code'); } + + public function getMaxFrequencyPeriodNameAttribute() + { + return self::$max_frequency_periods[$this->max_frequency_period] ?? null; + } + + public function getMaxFrequencyAttribute() + { + switch ($this->max_frequency_period) { + // case(0) : + // // TODO Fix This + // return null; + // break; + case(1) : + return empty($this->daily_frequency) ? 1 : $this->daily_frequency; + break; + case(2) : + return empty($this->weekly_frequency) ? 1 : $this->weekly_frequency; + break; + case(3) : + return empty($this->monthly_frequency) ? 1 : $this->monthly_frequency; + break; + case(4) : + return empty($this->yearly_frequency) ? 1 : $this->yearly_frequency; + break; + case(5) : + // TODO Fix This + return empty($this->max_period_for_disability) ? 1 : $this->max_period_for_disability; + break; + case(6) : + // TODO Fix This + return 1; + break; + default : + return null; + break; + } + } } diff --git a/app/Models/Claim.php b/app/Models/Claim.php index 5ce7cf10..350bc5b2 100644 --- a/app/Models/Claim.php +++ b/app/Models/Claim.php @@ -62,6 +62,11 @@ class Claim extends Model }); } + public function files() + { + return $this->morphMany(File::class, 'fileable'); + } + public function member() { return $this->belongsTo(Member::class, 'member_id'); diff --git a/app/Models/Member.php b/app/Models/Member.php index afe28934..d8496645 100755 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -99,17 +99,24 @@ class Member extends Model 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()); + // 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()); + + + return $this->hasOneThrough(Corporate::class, CorporateEmployee::class, 'member_id', 'id', 'id', 'corporate_id'); + // ->where('corporate_policies.start', '<', now()) + // ->where('corporate_policies.end', '>', now()) + // ->where('member_policies.start', '<', now()) + // ->where('member_policies.end', '>', now()); } public function memberPlans() diff --git a/app/Services/ClaimService.php b/app/Services/ClaimService.php index 95a49022..76858959 100644 --- a/app/Services/ClaimService.php +++ b/app/Services/ClaimService.php @@ -10,23 +10,6 @@ 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;asldkmalskdmalksmdalksmd - } - public static function getMemberTotalUsage(Member $member, $startDate = null, $endDate = null) { $startDate = empty($startDate) ? Carbon::now()->startOfMonth() : $startDate; @@ -41,4 +24,171 @@ class ClaimService{ return $total; } + + 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 getMemberUsageByBenefitLimit($member, $benefit) + { + + } + + public static function showMemberBenefitLimit($member, $benefit_code) + { + // $plan = $member->currentPlan; + // $policy = $member->currentPolicy; + // $corporate = $member->currentCorporate; + $benefit = $member->currentPlan->benefits()->where('code', $benefit_code)->first(); + + // dd($benefit->toArray()); + // dd(compact(['plan', 'policy', 'corporate', 'benefit'])); + $limits = [ + 'total_limit' => $benefit->limit_amount, + 'frequency_limit_name' => $benefit->max_frequency_period_name, + 'frequency_limit' => $benefit->max_frequency, + 'total_claim' => 0, + 'remaining_limit' => $benefit->limit_amount, + 'usage_daily' => null, + 'usage_weekly' => null, + 'usage_monthly' => null, + 'usage_yearly' => null + ]; + + switch ($benefit->max_frequency_period) { + case(0) : + $limits['usage_yearly'] = $member->claims()->used(Carbon::now()->firstOfYear(), now())->count(); + $limits['total_claim'] = $member->claims()->used(Carbon::now()->firstOfYear(), now())->sum('total_claim'); + break; + case(1) : + $limits['usage_daily'] = $member->claims()->used(now()->format('Y-m-d'), now()->addDay(1)->format('Y-m-d'))->count(); + $limits['total_claim'] = $member->claims()->used(now()->format('Y-m-d'), now()->addDay(1)->format('Y-m-d'))->sum('total_claim'); + break; + case(2) : + $limits['usage_weekly'] = $member->claims()->used(Carbon::parse('Previous Sunday'), now())->count(); + $limits['total_claim'] = $member->claims()->used(Carbon::parse('Previous Sunday'), now())->sum('total_claim'); + break; + case(3) : + $limits['usage_monthly'] = $member->claims()->used(Carbon::now()->firstOfMonth(), now())->count(); + $limits['total_claim'] = $member->claims()->used(Carbon::now()->firstOfMonth(), now())->sum('total_claim'); + break; + case(4) : + $limits['usage_yearly'] = $member->claims()->used(Carbon::now()->firstOfYear(), now())->count(); + $limits['total_claim'] = $member->claims()->used(Carbon::now()->firstOfYear(), now())->sum('total_claim'); + break; + default : + // return null; + break; + } + $limits['remaining_limit'] = $benefit->limit_amount - $limits['total_claim']; + + return $limits; + } + + public static function storeClaim($member, $diagnosis, $totalClaim, $benefit, $status) + { + try { + DB::beginTransaction(); + + $claimData = [ + 'member_id' => $member->id, + 'diagnosis_id' => $diagnosis->id, + 'total_claim' => $totalClaim, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'benefit_id' => $benefit->id, + 'status' => $status + ]; + $claimData[$status.'_at'] = now(); + $claimData[$status.'_by'] = auth()->user()->id ?? null; + + $claim = Claim::create($claimData); + + $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(); + + throw new \Exception($error); + } + } } \ No newline at end of file diff --git a/frontend/dashboard/src/pages/Claims/CreateUpdate.tsx b/frontend/dashboard/src/pages/Claims/CreateUpdate.tsx index 9276efa3..58bb43d6 100755 --- a/frontend/dashboard/src/pages/Claims/CreateUpdate.tsx +++ b/frontend/dashboard/src/pages/Claims/CreateUpdate.tsx @@ -1,6 +1,6 @@ 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 { Autocomplete, Button, Card, Collapse, Container, 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'; @@ -15,414 +15,50 @@ import { enqueueSnackbar } from 'notistack'; import { LoadingButton } from '@mui/lab'; import { fCurrency } from '../../utils/formatNumber'; import Iconify from '../../components/Iconify'; +import Form from './Form'; export default function ClaimsCreateUpdate() { - const navigate = useNavigate(); - - const [member, setMember] = useState(); - const selectedMemberDisplay = useRef(null); + + const { themeStretch } = useSettings(); const { id } = useParams(); - - const isEdit = !!id; - 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 isEdit = id ? true : false; - const defaultValues = useMemo( - () => ({ - member_id: null, - benefit_id: null, - diagnosis_id: null, - total_claim: 0, - benefit: null - }), - [] - ); + const [currentClaim, setCurrentClaim] = useState(); - 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 - - }, []) - useEffect(() => { if (isEdit) { - axios.get(`/claims/${id}`) - .then((res) => { - // console.log('fuck', res.data) - // setCurrentCorporatePlan(res.data); - set - }) - .catch((err) => { - // if (err.response.status === 404) { - // navigate('/404'); - // } - }) + axios.get('/claims/' + id).then((res) => { + // console.log('Yeet', res.data); + setCurrentClaim(res.data); + }); } }, [id]); - const [isEligible, setIsEligible] = useState(null) - - const [isCheckingLimit, setIsCheckingLimit] = useState(false) - const checkLimit = (event) => { - event.preventDefault(); - - console.log(getValues('diagnosis_id')) - if (!member || !getValues('diagnosis_id')) { - enqueueSnackbar('Please Check the Data', { variant: 'error' }) - - return false; - } - - setIsCheckingLimit(true) - axios.post('check-limit', { - 'member_id' : member.id, - 'diagnosis' : getValues('diagnosis_id'), - 'total_claim' : getValues('total_claim') - }) - .then((res) => { - setIsEligible(true) - }) - .catch((err) => { - enqueueSnackbar('Failed Checking Limit : ' + err.message ?? '', { variant: 'error' }) - }) - .then(() => { - setIsCheckingLimit(false) - }) - } - const headStyle = { - fontWeight: 'bold' - }; - return ( - - + + + + + - - - - - - - Member - - - - {setIsMemberDialogOpen(true)}} - /> - - {/* - - */} - - - { member && ( - - - - - - - Name - {member.full_name} - - - DOB - {member.birth_date} ({member.age + ' years'}) - - - Marital Status - {member.marital_status} - - - Record Type - {member.record_type} - - - Principal ID - {member.principal_id} ({member.relation_with_principal}) - - -
-
- - - - - Plan - {member.current_plan.code} - - - Active - {member.current_plan.start} - {member.current_plan.end} (Active) - - - Corporate Limit - {fCurrency(0)} / {fCurrency(member.current_plan.limit_rules)} - - - Plan Usage - {fCurrency(0)} / {fCurrency(member.current_plan.limit_rules)} - - -
-
-
-
- )} - - ( - - option ? `#${option.id} (${option.code}) ${option.description}` : '' - } - value={value || ''} - onChange={(event: any, newValue: any) => { - setValue('benefit_id', newValue?.id) - onChange(newValue); - }} - renderInput={(params) => ( - { - // if (event.key === 'Enter') - // searchDiagnosis(event.target.value) - // }} - /> - )} - /> - )} - /> - - ( - - option ? `(${option.code}) ${option.name}` : '' - } - value={value || ''} - onChange={(event: any, newValue: any) => { - setValue('diagnosis_id', newValue?.id) - // setValue('diagnosis', newValue) - onChange(newValue); - }} - renderInput={(params) => ( - { - if (event.key === 'Enter') - searchDiagnosis(event.target.value) - }} - /> - )} - /> - )} - /> - - { isCheckingLimit && ( - - {/* Checking */} - - - - Please Wait, Checking Eligibilty - - - - )} - { false && isCheckingLimit == false && isEligible == null && ( - - {/* No Data Selected */} - - - - Please Select Diagnosis ! - - - - )} - { (!isCheckingLimit && isEligible !== null) && isEligible && ( - - {/* Eligible */} - - - - Diagnosis is Eligible - - - - 125.000.000 / 125.000.000 - - - )} - { (!isCheckingLimit && isEligible !== null) && !isEligible && ( - - {/* Not Eligible */} - - - - Not Eligible - - - - 125.000.000 / 125.000.000 - - - )} - - - - - - { isEligible === true ? ( - - Create Claim - - ) : ( - - Check Limit - - )} - -
-
-
-
-
- - +
+ ); } diff --git a/frontend/dashboard/src/pages/Claims/Form.tsx b/frontend/dashboard/src/pages/Claims/Form.tsx index e69de29b..0da987d1 100644 --- a/frontend/dashboard/src/pages/Claims/Form.tsx +++ b/frontend/dashboard/src/pages/Claims/Form.tsx @@ -0,0 +1,596 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller, useForm } from 'react-hook-form'; +import React, { useEffect, useMemo, useState } from 'react'; +import axios from '../../utils/axios'; +import { FormProvider, RHFTextField } from '../../components/hook-form'; +import { + Autocomplete, + Button, + Grid, + Stack, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + useTheme, + List, + ListItem, + IconButton, + ListItemAvatar, + Avatar, + ListItemText, +} from '@mui/material'; +import Iconify from '../../components/Iconify'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../utils/formatNumber'; +import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; +import { Add, DeleteOutline } from '@mui/icons-material'; + +type Props = { + isEdit: boolean; + currentClaim?: any; +}; + +export default function ClaimForm({ isEdit, currentClaim }: Props) { + const navigate = useNavigate(); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + code: Yup.string().required('Corporate Code is required'), + active: Yup.boolean().required('Corporate Status is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + member: currentClaim?.member || {}, + member_id: currentClaim?.member_id || null, + diagnosis_id: currentClaim?.diagnosis_id || null, + total_claim: currentClaim?.total_claim || 0, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentClaim] + ); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + const [isCheckingLimit, setIsCheckingLimit] = useState(false); + const [isEligible, setIsEligible] = useState(false); + const [memberBenefits, setMemberBenefits] = useState([]); + const [diagnosisOption, setDiagnosisOption] = useState([]); + const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); + const [member, setMember] = useState({}) + + + useEffect(() => { + console.log('defaultValues', defaultValues); + if (isEdit && currentClaim) { + reset(defaultValues); + setMember(defaultValues.member) + } + if (!isEdit) { + reset(defaultValues); + setMember(defaultValues.member) + } + }, [isEdit, currentClaim]); + + const fileSelected = (event, type) => { + const files = event.target.files; + const currentFiles = getValues(`uploaded_files.${type}`) ?? []; + + setValue(`uploaded_files.${type}`, [...currentFiles, ...files]); + + console.log('currentFiles', getValues('uploaded_files')); + }; + + const memberSelected = (member) => { + setMember(member) + }; + + const checkLimit = async () => { + console.log('CHECKING LIMIT'); + }; + + const onSubmit = async (data: any) => { + try { + if (!isEdit) { + const response = await axios.post('/claims', data); + } else { + const response = await axios.put('/claims/' + currentClaim?.id ?? '', data); + } + reset(); + enqueueSnackbar( + !isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!', + { variant: 'success' } + ); + navigate('/claims'); + } catch (error: any) { + if (error && error.response.status === 422) { + for (const [key, value] of Object.entries(error.response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + } + } else { + enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); + } + } + + const ascent = document?.querySelector('ascent'); + if (ascent != null) { + ascent.innerHTML = ''; + } + }; + + function generate(files, element: React.ReactElement) { + return files.map((value) => + React.cloneElement(element, { + key: value, + }) + ); + } + + const headStyle = {}; + return ( + + + Member + + + + { + if (!isEdit) setIsMemberDialogOpen(true); + if (isEdit) enqueueSnackbar('Cannot Change Member', { variant: 'error' }); + }} + /> + + {/* + + */} + + + {member?.id && ( + + + + + + + + Name + + {member?.full_name} + + + + DOB + + + {member?.birth_date} ({member?.age + ' years'}) + + + + + Marital Status + + {member?.marital_status} + + + + Record Type + + {member?.record_type} + + + + Principal ID + + + {member?.principal_id} ( + {member?.relation_with_principal}) + + + +
+
+ + + + + + Plan + + {member?.current_plan?.code} + + + + Active + + + {member?.current_plan?.start} -{' '} + {member?.current_plan?.end} (Active) + + + + + Corporate Limit + + + {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} + + + + + Plan Usage + + + {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} + + + +
+
+
+
+ )} + + ( + + option ? `#${option.id} (${option.code}) ${option.description}` : '' + } + value={value || ''} + onChange={(event: any, newValue: any) => { + setValue('benefit_id', newValue?.id); + onChange(newValue); + }} + renderInput={(params) => ( + { + // if (event.key === 'Enter') + // searchDiagnosis(event.target.value) + // }} + /> + )} + /> + )} + /> + + ( + (option ? `(${option.code}) ${option.name}` : '')} + value={value || ''} + onChange={(event: any, newValue: any) => { + setValue('diagnosis_id', newValue?.id); + // setValue('diagnosis', newValue) + onChange(newValue); + }} + renderInput={(params) => ( + { + if (event.key === 'Enter') searchDiagnosis(event.target.value); + }} + /> + )} + /> + )} + /> + + {isCheckingLimit && ( + + {/* Checking */} + + + + Please Wait, Checking Eligibilty + + + + )} + {false && isCheckingLimit == false && isEligible == null && ( + + {/* No Data Selected */} + + + + Please Select Diagnosis ! + + + + )} + {!isCheckingLimit && isEligible !== null && isEligible && ( + + {/* Eligible */} + + + + Diagnosis is Eligible + + + + 125.000.000 / 125.000.000 + + + )} + {!isCheckingLimit && isEligible !== null && !isEligible && ( + + {/* Not Eligible */} + + + + Not Eligible + + + + 125.000.000 / 125.000.000 + + + )} + + + + {isEdit && ( + + Documents + + + {(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length + ? getValues('uploaded_files.invoice') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + I + + + + + ))} + + + + {(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length + ? getValues('uploaded_files.prescription') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + P + + + + + ))} + + + + + {(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length + ? getValues('uploaded_files.diagnosis') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + DR + + + + + ))} + + + + )} + + {isEligible === true ? ( + + Create Claim + + ) : ( + + Check Limit + + )} +
+ + +
+ ); +} diff --git a/frontend/dashboard/src/pages/Claims/Index.tsx b/frontend/dashboard/src/pages/Claims/Index.tsx index 15c2d7d8..5fc6d983 100755 --- a/frontend/dashboard/src/pages/Claims/Index.tsx +++ b/frontend/dashboard/src/pages/Claims/Index.tsx @@ -22,9 +22,9 @@ export default function Claims() { ]} /> - - - + {/* */} + + {/* */} ); } diff --git a/frontend/dashboard/src/pages/Claims/List.tsx b/frontend/dashboard/src/pages/Claims/List.tsx index 16b2560f..dee1db94 100755 --- a/frontend/dashboard/src/pages/Claims/List.tsx +++ b/frontend/dashboard/src/pages/Claims/List.tsx @@ -1,5 +1,22 @@ // @mui -import { Box, Button, Card, Collapse, IconButton, MenuItem, Table, TableBody, TableCell, TableRow, TextField, Typography, Stack, Menu, ButtonGroup, Link } from '@mui/material'; +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'; @@ -7,7 +24,7 @@ import UploadIcon from '@mui/icons-material/Upload'; import CancelIcon from '@mui/icons-material/Cancel'; // hooks import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; -import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; // components import axios from '../../utils/axios'; import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data'; @@ -19,29 +36,38 @@ export default function List() { const [searchParams, setSearchParams] = useSearchParams(); const [importResult, setImportResult] = useState(null); const navigate = useNavigate(); - + function SearchInput(props: any) { - // SEARCH + // SEARCH const searchInput = useRef(null); - const [searchText, setSearchText] = useState(""); + const [searchText, setSearchText] = useState(''); const handleSearchChange = (event: any) => { - const newSearchText = event.target.value ?? '' + const newSearchText = event.target.value ?? ''; setSearchText(newSearchText); - } + }; const handleSearchSubmit = (event: any) => { event.preventDefault(); - props.onSearch({search: searchText }); // Trigger to Parent - } + props.onSearch({ search: searchText }); // Trigger to Parent + }; - useEffect(() => { // Trigger First Search + useEffect(() => { + // Trigger First Search setSearchText(searchParams.get('search') ?? ''); - }, []) + }, []); return ( - + ); } @@ -51,8 +77,8 @@ export default function List() { // Create Button Menu const [anchorEl, setAnchorEl] = React.useState(null); const createMenu = Boolean(anchorEl); - const importForm = useRef(null) - const [currentImportFileName, setCurrentImportFileName] = useState(null) + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -67,54 +93,61 @@ export default function List() { handleClose(); importForm.current ? importForm.current.click() : console.log('No File selected'); } else { - alert('No file selected') + alert('No file selected'); } - } + }; const handleCancelImportButton = () => { - importForm.current.value = ""; - importForm.current.dispatchEvent(new Event("change", { bubbles: true })); - } + 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) + 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 => { + formData.append('file', importForm.current?.files[0]); + axios + .post(`corporates/${corporate_id}/import-plan-benefit`, formData) + .then((response) => { handleCancelImportButton(); loadDataTableData(); - setImportResult(response.data) + 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' }) - }) + .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' }) + enqueueSnackbar('No File Selected', { variant: 'warning' }); } - } - + }; + return (
- - - - + +
); @@ -122,9 +155,11 @@ export default function List() { // Dummy Default Data const [dataTableIsLoading, setDataTableLoading] = useState(true); - const [dataTableData, setDataTableData] = useState(LaravelPaginatedDataDefault); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); - const loadDataTableData = async (appliedFilter : any | null = null) => { + 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 }); @@ -132,35 +167,37 @@ export default function List() { setDataTableLoading(false); setDataTableData(response.data); - } + }; - const applyFilter = async (searchFilter: {search: string}) => { + 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]]); + 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', + fontWeight: 'bold', }; // Called on every row to map the data to the columns - function createData( data: any ): any { + function createData(data: any): any { return { ...data, - } + }; } - {/* ------------------ TABLE ROW ------------------ */} + { + /* ------------------ TABLE ROW ------------------ */ + } function Row(props: { row: ReturnType }) { const { row } = props; const [open, setOpen] = React.useState(false); @@ -169,11 +206,7 @@ export default function List() { *': { borderBottom: 'unset' } }}> - setOpen(!open)} - > + setOpen(!open)}> {open ? : } @@ -181,10 +214,18 @@ export default function List() { {row.member?.full_name} {row.plan?.code} {row.benefit?.code} - ({row.diagnosis?.code}) {row.diagnosis?.name} + + ({row.diagnosis?.code}) {row.diagnosis?.name} + {fCurrency(row.total_claim)} - {navigate('/claims/'+row.id)}}/> + + { + navigate('/claims/' + row.id); + }} + /> + {/* COLLAPSIBLE ROW */} @@ -201,7 +242,9 @@ export default function List() { ); } - {/* ------------------ END TABLE ROW ------------------ */} + { + /* ------------------ END TABLE ROW ------------------ */ + } function TableContent() { return ( @@ -210,57 +253,69 @@ export default function List() { - Code - Member Name - Plan - Benefit - Diagnosis - Total Claim - Action + + Code + + + Member Name + + + Plan + + + Benefit + + + Diagnosis + + + Total Claim + + + Action + {/* ------------------ END TABLE HEADER ------------------ */} - {/* ------------------ TABLE ROW ------------------ */} - {dataTableIsLoading ? - ( - - - Loading - - - ) : ( - dataTableData.data.length === 0 ? - ( - - - No Data - - - ) : ( - - {dataTableData.data.map(row => ( - - ))} - - ) + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + )} {/* ------------------ END TABLE ROW ------------------ */} - ) + ); } - + return ( - } /> diff --git a/frontend/dashboard/src/pages/Corporates/CorporatePlan/Form.tsx b/frontend/dashboard/src/pages/Corporates/CorporatePlan/Form.tsx index b680e2b6..236a4e01 100755 --- a/frontend/dashboard/src/pages/Corporates/CorporatePlan/Form.tsx +++ b/frontend/dashboard/src/pages/Corporates/CorporatePlan/Form.tsx @@ -1,14 +1,15 @@ import * as Yup from 'yup'; -import { LoadingButton } from "@mui/lab"; -import { Card, Grid, Stack, Typography } from "@mui/material"; -import { CorporatePlan } from "../../../@types/corporates"; -import { FormProvider, RHFEditor, RHFSwitch, RHFTextField } from "../../../components/hook-form"; -import { useEffect, useMemo } from 'react'; +import { LoadingButton } from '@mui/lab'; +import { Card, Grid, Stack, Typography } from '@mui/material'; +import { CorporatePlan } from '../../../@types/corporates'; +import { FormProvider, RHFEditor, RHFSwitch, RHFTextField } from '../../../components/hook-form'; +import { useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { useSnackbar } from 'notistack'; import { useNavigate, useParams } from 'react-router-dom'; import axios from '../../../utils/axios'; +import MemberSelectDialog from '../../../components/dialogs/MemberSelectDialog'; type Props = { isEdit: boolean; @@ -16,7 +17,6 @@ type Props = { }; export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Props) { - const { enqueueSnackbar } = useSnackbar(); const navigate = useNavigate(); const { corporate_id } = useParams(); @@ -31,7 +31,7 @@ export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Prop name: currentCorporatePlan?.name || '', code: currentCorporatePlan?.code || '', active: currentCorporatePlan?.active || true, - description: currentCorporatePlan?.description || '' + description: currentCorporatePlan?.description || '', }), [currentCorporatePlan] ); @@ -61,75 +61,78 @@ export default function CorporatePlanForm({ isEdit, currentCorporatePlan }: Prop formState: { isSubmitting }, } = methods; - const onSubmit = async (data: any) => { if (!isEdit) { await axios - .post('/corporates/' + corporate_id + '/corporate-plans', data) - .then((res) => { - enqueueSnackbar('Corporate Plan created successfully', { variant: 'success' }); - }) - .then((res) => { - navigate('/corporates/' + corporate_id + '/corporate-plans', { replace: true }); - }) - .catch(({ response }) => { - if (response.status === 422) { - for (const [key, value] of Object.entries(response.data.errors)) { - setError(key, { message: value[0] }); - enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + .post('/corporates/' + corporate_id + '/corporate-plans', data) + .then((res) => { + enqueueSnackbar('Corporate Plan created successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/corporates/' + corporate_id + '/corporate-plans', { replace: true }); + }) + .catch(({ response }) => { + if (response.status === 422) { + for (const [key, value] of Object.entries(response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); + } + } else { + enqueueSnackbar('Create Failed : ' + response.data.message, { variant: 'error' }); } - } - else { - enqueueSnackbar('Create Failed : '+ response.data.message, { variant: 'error' }); - } - }); + }); } else { await axios - .put('/corporates/' + corporate_id + '/corporate-plans/' + currentCorporatePlan?.id , data) + .put('/corporates/' + corporate_id + '/corporate-plans/' + currentCorporatePlan?.id, data) .then((res) => { enqueueSnackbar('Corporate Plan updated successfully', { variant: 'success' }); }) .then((res) => { - navigate('/corporates/' + corporate_id + '/corporate-plans/' , { replace: true }); + navigate('/corporates/' + corporate_id + '/corporate-plans/', { replace: true }); }) .catch(({ response }) => { - enqueueSnackbar('Update Failed : '+ response.data.message, { variant: 'error' }); + enqueueSnackbar('Update Failed : ' + response.data.message, { variant: 'error' }); }); } }; return ( - - - - + + + + + Corporate Plan Detail - Corporate Plan Detail + - + - + + + Description + + + - - Description - - - - - Create Corporate Plan - - - - - - - - - - - + + Create Corporate Plan + + + - + + + + + + + ); } diff --git a/routes/api.php b/routes/api.php index cf3dbc81..c33577fd 100755 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,7 @@ group(function() { + Route::post('check-membership', [MembershipController::class, 'check']); + Route::post('check-limit', [MembershipController::class, 'checkLimit']); +}); \ No newline at end of file