diff --git a/Modules/Internal/Http/Controllers/Api/MemberController.php b/Modules/Internal/Http/Controllers/Api/MemberController.php index fe178a21..c31d7c0b 100755 --- a/Modules/Internal/Http/Controllers/Api/MemberController.php +++ b/Modules/Internal/Http/Controllers/Api/MemberController.php @@ -26,7 +26,7 @@ class MemberController extends Controller */ public function index(Request $request, $corporate_id) { - $benefits = Member::query() + $members = Member::query() ->filter($request->all()) // ->where('corporate_id', $corporate_id) ->whereHas('employeds', function ($employeds) use ($corporate_id) { @@ -40,10 +40,11 @@ class MemberController extends Controller }); } ]) + ->with('currentPlan') ->paginate() ->appends($request->all()); - return $benefits; + return $members; } /** diff --git a/Modules/Internal/Services/MemberEnrollmentService.php b/Modules/Internal/Services/MemberEnrollmentService.php index 011ea50f..fc45e427 100755 --- a/Modules/Internal/Services/MemberEnrollmentService.php +++ b/Modules/Internal/Services/MemberEnrollmentService.php @@ -423,6 +423,15 @@ class MemberEnrollmentService ]), 0, null, $row); } + // Validate If Plan Exist + // TODO validate corporate plan + $plan = Plan::query() + ->where('code', $row['plan_id']) + ->first(); + if (!$plan) { + throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); + } + $this->validateRow($row); try { @@ -450,6 +459,13 @@ class MemberEnrollmentService 'nik' => $row['nik'], 'status' => $row['employment_status'] ]); + + $member->memberPlans()->create([ + 'plan_id' => $plan->code, + '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 +474,65 @@ class MemberEnrollmentService } break; case "2": // Member Information Update (Without Replacement Card) - $memberPolicy = MemberPolicy::query() - ->where('policy_id', $row['policy_number']) - ->where('member_id', $row['member_id']) - ->with('member') - ->first(); + $member = Member::query() + ->where('member_id', $row['member_id']) + ->first(); + + // Validate If Exist Member + if (!$member) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_FOUND', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } - if (!$memberPolicy) { - throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); + try { + $memberPolicy = MemberPolicy::query() + ->where('policy_id', $row['policy_number']) + ->where('member_id', $row['member_id']) + ->with('member') + ->first(); + + if (!$memberPolicy) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + if ($memberPolicy->status != 'active') { + throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + + $memberPolicy->member->fill($member_data); + if (!$memberPolicy->member->isDirty()) { + throw new ImportRowException(__('enrollment.MEMBER_NO_CHANGE'), 0, null, $row); + } + + $memberPolicy->member->save(); + DB::commit(); + } catch (\Exception $e) { + DB::rollback(); + throw new ImportRowException($e->getMessage(), $e->getCode(), $e, $row); } - - if ($memberPolicy->status != 'active') { - throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [ - 'member_id' => $row['member_id'], - 'policy_id' => $row['policy_number'] - ]), 0, null, $row); - } - - $memberPolicy->member->fill($member_data); - if (!$memberPolicy->member->isDirty()) { - throw new ImportRowException(__('enrollment.MEMBER_NO_CHANGE'), 0, null, $row); - } - - $memberPolicy->member->save(); break; case "3": // Member Deletion + $member = Member::query() + ->where('member_id', $row['member_id']) + ->first(); + + // Validate If Exist Member + if (!$member) { + throw new ImportRowException(__('enrollment.MEMBER_NOT_FOUND', [ + 'member_id' => $row['member_id'], + 'policy_id' => $row['policy_number'] + ]), 0, null, $row); + } + $memberPolicy = MemberPolicy::query() ->where('policy_id', $row['policy_number']) ->where('member_id', $row['member_id']) diff --git a/app/Models/Member.php b/app/Models/Member.php index ea1657d7..3ddf2b4c 100755 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -56,6 +56,11 @@ class Member extends Model "end_no_claim", ]; + public function claims() + { + return $this->hasMany(Claim::class, 'member_id', 'id'); + } + public function employeds() { return $this->hasMany(CorporateEmployee::class, 'member_id'); @@ -68,7 +73,12 @@ 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', 'code', 'id', 'plan_id'); + } + + public function currentPlan() + { + return $this->hasOneThrough(Plan::class, MemberPlan::class, 'member_id', 'code', 'id', 'plan_id')->latest(); } public function policies() diff --git a/frontend/dashboard/src/@types/paginated-data.ts b/frontend/dashboard/src/@types/paginated-data.ts index 738f8785..eca6d930 100755 --- a/frontend/dashboard/src/@types/paginated-data.ts +++ b/frontend/dashboard/src/@types/paginated-data.ts @@ -12,3 +12,18 @@ export type LaravelPaginatedData = { to?: number; total: number; } + +export const LaravelPaginatedDataDefault = { + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 +} \ No newline at end of file diff --git a/frontend/dashboard/src/pages/Claims/Create.tsx b/frontend/dashboard/src/pages/Claims/Create.tsx new file mode 100755 index 00000000..5e9fccdf --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/Create.tsx @@ -0,0 +1,241 @@ +import * as Yup from 'yup'; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material"; +import { useForm } from "react-hook-form"; +import { useParams } 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 CorporateTabNavigations from "../CorporateTabNavigations"; +import DivisionsList from "./List"; +import { useMemo, useState } from 'react'; + + + +export default function Divisions() { + const { themeStretch } = useSettings(); + + const { corporate_id } = useParams(); + + const NewDivisionSchema = 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'), + }); + + const defaultValues = useMemo( + () => ({ + code: '', + }), + [] + ); + + const methods = useForm({ + resolver: yupResolver(NewDivisionSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const onSubmit = async (data: any) => { + console.log(data); + }; + + const [open, setOpen] = useState(false); + + const benefits = [ + { + 'category' : 'General Practitioner', + 'childs' : [ + { + 'name' : 'External Doctor Online', + 'code' : 'gp-external-doctor-online' + }, + { + 'name' : 'External Doctor Offline', + 'code' : 'gp-external-doctor-offline' + }, + { + 'name' : 'Internal Doctor Online', + 'code' : 'gp-internal-doctor-online' + }, + { + 'name' : 'Internal Doctor Offline', + 'code' : 'gp-internal-doctor-offline' + }, + ] + }, + { + 'category' : 'Specialist', + 'childs' : [ + { + 'name' : 'External Doctor Online', + 'code' : 'sp-external-doctor-online' + }, + { + 'name' : 'External Doctor Offline', + 'code' : 'sp-external-doctor-offline' + }, + { + 'name' : 'Internal Doctor Online', + 'code' : 'sp-internal-doctor-online' + }, + { + 'name' : 'Internal Doctor Offline', + 'code' : 'sp-internal-doctor-offline' + }, + ] + }, + { + 'category' : 'Medicines', + 'childs' : [ + { + 'name' : 'Vitamins', + 'code' : 'medicines-vitamins' + }, + { + 'name' : 'Delivery Fee', + 'code' : 'medicines-delivery-fee' + }, + ] + }, + ]; + + const products = [ + { + 'name' : 'Inpatient', + 'code' : 'IP', + }, + { + 'name' : 'Outpatient', + 'code' : 'OP', + }, + { + 'name' : 'Dental', + 'code' : 'DT', + }, + { + 'name' : 'Dental', + 'code' : 'DTL', + }, + { + 'name' : 'Matternity', + 'code' : 'MT', + }, + { + 'name' : 'Special Benefit', + 'code' : 'SB', + }, + ]; + + return ( + + + + + + + + + + + + Benefit Detail + + + + + + Benefit Configuration + + + }> + + + {benefits.map(row => ( + + {row.category} + + {row.childs.map(benefit => ( + + + + ))} + + + ))} + Admin Fee + + {benefits.map(row => ( + + + + ))} + + + + + + + {benefits.map(row => ( + + {row.category} + + {row.childs.map(benefit => ( + + + + ))} + + + ))} + Admin Fee + + {benefits.map(row => ( + + + + ))} + + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Claims/Index.tsx b/frontend/dashboard/src/pages/Claims/Index.tsx new file mode 100755 index 00000000..0c5ba319 --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/Index.tsx @@ -0,0 +1,29 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +import Page from "../../components/Page"; +import List from "./List"; + + + +export default function Claims() { + + const pageTitle = 'Claim'; + return ( + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Claims/LaravelTable.tsx b/frontend/dashboard/src/pages/Claims/LaravelTable.tsx new file mode 100644 index 00000000..0ee480e1 --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/LaravelTable.tsx @@ -0,0 +1,26 @@ +import { Card, Paper, TableContainer } from "@mui/material"; +import { LaravelPaginatedData } from "../../@types/paginated-data"; +import BasePagination from "../../components/BasePagination"; + +type LaravelTableProps = { + isLoading: boolean; + lastRequest: number; + data: LaravelPaginatedData; + handlePageChange: void; + TableContent: any; +}; + +export default function LaravelTable(props: LaravelTableProps) { + return ( + + + { props.TableContent } + + + { !props.isLoading ? + () : + (
) + } +
+ ) +} \ No newline at end of file diff --git a/frontend/dashboard/src/pages/Claims/List.tsx b/frontend/dashboard/src/pages/Claims/List.tsx new file mode 100755 index 00000000..79772d12 --- /dev/null +++ b/frontend/dashboard/src/pages/Claims/List.tsx @@ -0,0 +1,303 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, MenuItem, Table, TableBody, TableCell, TableRow, TextField, Typography, Stack, Menu, ButtonGroup } 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 './LaravelTable'; + +export default function List() { + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({search: searchText }); // Trigger to Parent + } + + useEffect(() => { // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []) + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null) + const [currentImportFileName, setCurrentImportFileName] = useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importForm?.current) { + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected') + } + } + + const handleCancelImportButton = () => { + importForm.current.value = ""; + importForm.current.dispatchEvent(new Event("change", { bubbles: true })); + } + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name) + } else { + setCurrentImportFileName(null); + } + } + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append("file", importForm.current?.files[0]) + axios.post(`corporates/${corporate_id}/import-plan-benefit`, formData ) + .then(response => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data) + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + }) + .catch(response => { + enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) + }) + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }) + } + } + + return ( +
+ + {( !currentImportFileName && + + {/*

kjasndkjandskjasndkjansdkjansd

*/} + + + Import + Download Template + +
+ )} + + {( currentImportFileName && + + + + + + + + )} + {( importResult && + + Last Import Result Report : {importResult.result_file?.name ?? "-"} + + )} +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState(LaravelPaginatedDataDefault); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/master/drugs', { 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: Icd ): Icd { + return { + ...data, + } + } + + {/* ------------------ TABLE ROW ------------------ */} + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)} + > + {open ? : } + + + {row.type} + {row.code} + {row.name} + {row.version} + + + + + {/* COLLAPSIBLE ROW */} + + + + + + Description : {row.description} + + + + + + + ); + } + {/* ------------------ END TABLE ROW ------------------ */} + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + + Type + Code + Name + Version + Status + Action + + + {/* ------------------ END TABLE HEADER ------------------ */} + + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? + ( + + + Loading + + + ) : ( + dataTableData.data.length === 0 ? + ( + + + No Data + + + ) : ( + + {dataTableData.data.map(row => ( + + ))} + + ) + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ) + } + + return ( + + + + } + /> + + ); +} diff --git a/frontend/dashboard/src/pages/Corporates/Member/List.tsx b/frontend/dashboard/src/pages/Corporates/Member/List.tsx index 371349fa..b60c8ec4 100755 --- a/frontend/dashboard/src/pages/Corporates/Member/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Member/List.tsx @@ -268,7 +268,7 @@ export default function CorporatePlanList() { {row.name} {row.nric} {row.email} - {row.plan_id} + {row.current_plan?.code} {row.current_policy?.start} {row.current_policy?.end} {( row.active ? () diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 0f977c1e..65e8f05b 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -205,6 +205,12 @@ export default function Router() { path: 'master/formularium/create', element: , }, + + + { + path: 'claims', + element: , + }, ] }, // { @@ -287,3 +293,5 @@ 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'))); \ No newline at end of file