From 6deaf27bcaf8ab505ed4326e4ef882d7f1d6d08d Mon Sep 17 00:00:00 2001 From: ivan-sim Date: Thu, 27 Feb 2025 16:55:25 +0700 Subject: [PATCH] Invoice Payment --- .../Http/Controllers/Api/ClaimController.php | 63 + .../Api/InvoicePaymentController.php | 627 +++++++ Modules/Internal/Routes/api.php | 11 +- app/Models/File.php | 1 + ...1_140114_create_invoice_payments_table.php | 40 + ...3_create_invoice_payment_details_table.php | 37 + .../pages/InvoicePayment/CreateInvoice.tsx | 1573 +++++++++++++++++ .../src/pages/InvoicePayment/Detail.tsx | 323 ++++ .../src/pages/InvoicePayment/Index.tsx | 30 + .../src/pages/InvoicePayment/List.tsx | 1063 +++++++++++ frontend/dashboard/src/routes/index.tsx | 23 + 11 files changed, 3790 insertions(+), 1 deletion(-) create mode 100644 Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php create mode 100644 database/migrations/2025_02_21_140114_create_invoice_payments_table.php create mode 100644 database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php create mode 100644 frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/Detail.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/Index.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/List.tsx diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php index 9202ecb7..2a4bfe3f 100755 --- a/Modules/Internal/Http/Controllers/Api/ClaimController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -127,6 +127,69 @@ class ClaimController extends Controller return response()->json(Helper::paginateResources($results)); } + public function getClaimDetails(Request $request) + { + // Get the 'ids' array from the request + $ids = $request->input('ids'); + + // Ensure the 'ids' array is not empty + if (empty($ids)) { + return response()->json(['error' => 'No IDs provided'], 400); + } + + // Use the SQL Query Builder with whereIn to fetch data + $claimDetails = DB::table('claim_requests') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->whereIn('claim_requests.id', $ids) + ->select( + 'claim_requests.id', + 'request_logs.invoice_no', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'request_logs.created_at as submission_date', + 'request_logs.submission_date as addmission_date', + 'request_logs.discharge_date', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->get(); + + // Return the fetched claim details as a JSON response + return response()->json($claimDetails); + } + + public function filesProvider(Request $request) { $limit = $request->has('per_page') ? $request->input('per_page') : 50; diff --git a/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php b/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php new file mode 100644 index 00000000..02f085b8 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php @@ -0,0 +1,627 @@ +has('per_page') ? $request->input('per_page') : 10; + $results = DB::table('invoice_payments') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('invoice_payments.invoice_number', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('invoice_payments.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('invoice_payments.created_at', '<=', $end_date); + }); + }) + ->when($request->input('status') , function ($query, $status) { + $query->where(function ($query) use ($status) { + $query->where('invoice_payments.status', '=', $status); + }); + }) + // ->where('claim_management', '=', 1) + ->select( + 'invoice_payments.id', + 'invoice_payments.invoice_number', + 'invoice_payments.start_date', + 'invoice_payments.end_date', + 'invoice_payments.created_at', + 'invoice_payments.status', + ) + ->groupBy('invoice_payments.invoice_number') + ->paginate($limit); + + + + return response()->json(Helper::paginateResources($results)); + } + public function claim(Request $request) + { + $limit = $request->has('per_page') ? $request->input('per_page') : 10; + $results = DB::table('claim_requests') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->leftJoin('invoice_payment_details', function ($join) { + $join->on('invoice_payment_details.claim_request_id', '=', 'claim_requests.id') + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }) + // ->leftJoin('member_plans', 'member_plans.member_id', '=', 'members.id') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('members.name', 'like', "%" . $search . "%"); + $query->orWhere('claim_requests.code', 'like', "%" . $search . "%"); + $query->orWhere('request_logs.code', 'like', "%" . $search . "%"); + $query->orWhere('members.member_id', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('claim_requests.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('claim_requests.created_at', '<=', $end_date); + }); + }) + ->when($request->input('provider') , function ($query, $provider) { + $query->where(function ($query) use ($provider) { + $query->where('request_logs.organization_id', '=', $provider); + }); + }) + ->where('claim_management', '=', 1) + ->when($request->input('param') !== 'Edit', function ($query) { + $query->whereNotIn('claim_requests.id', function ($query) { + $query->select('claim_request_id') + ->from('invoice_payment_details'); + }); + }) + ->when($request->input('param') === 'Edit', function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $q->whereNotIn('claim_requests.id', function ($subquery) { + $subquery->select('claim_request_id') + ->from('invoice_payment_details') + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }) + ->orWhereIn('claim_requests.id', function ($subquery) use ($request) { + $subquery->select('claim_request_id') + ->from('invoice_payment_details') + ->where('invoice_payment_details.invoice_payment_id', $request->input('invoiceID')) + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }); + }); + }) + ->select( + 'claim_requests.id', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'claim_requests.created_at', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->groupBy('claim_requests.id') + ->paginate($limit); + + + + return response()->json(Helper::paginateResources($results)); + } + public function detail($id) + { + $invoice['invoice_payments'] = DB::table('invoice_payments') + ->where('invoice_payments.id', $id) + ->select('invoice_payments.*') + ->get(); + + $invoice['invoice_payment_details'] = DB::table('invoice_payment_details') + ->leftJoin('claim_requests', 'claim_requests.id', '=', 'invoice_payment_details.claim_request_id') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->where('invoice_payment_details.invoice_payment_id', $id) + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0) + ->select( + 'claim_requests.id', + 'request_logs.invoice_no', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'request_logs.created_at as submission_date', + 'request_logs.submission_date as addmission_date', + 'request_logs.discharge_date', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->groupBy('claim_requests.id') + ->get(); + $payments = DB::table('invoice_payments') + ->leftJoin('files', function ($join) { + $join->on('files.fileable_id', '=', 'invoice_payments.id') + ->where('files.type', 'files-proof-payment') + ->whereNull('files.deleted_by') + ->orWhere('files.deleted_by', 0); + }) + ->where('invoice_payments.invoice_number', $invoice['invoice_payments'][0]->invoice_number) + ->select( + 'invoice_payments.id', + 'files.id as file_id', + 'invoice_payments.amount_paid', + 'invoice_payments.payment_number', + DB::raw("CONCAT('" . env('APP_URL') . "/storage/', files.path) as path"), + 'files.original_name' + ) + ->orderBy('invoice_payments.payment_number', 'asc') + ->get(); + + $invoice['files'] = $payments->groupBy('payment_number')->map(function ($group) { + return [ + 'id' => $group->first()->id, + 'amount_paid' => $group->first()->amount_paid, + 'payment_number' => $group->first()->payment_number, + 'files' => $group->map(function ($file) { + return [ + 'file_id' => $file->file_id, + 'path' => $file->path, + 'original_name' => $file->original_name + ]; + })->toArray() + ]; + })->values()->toArray(); + + if ($invoice['invoice_payments']->isEmpty()) { + return response()->json(['message' => 'Invoice tidak ditemukan'], 404); + } + + return response()->json($invoice); + } + + public function create(Request $request) + { + $data = [ + 'invoice_number' => $request->invoice_number, + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->end_date, + 'payments' => $request->payments + ]; + $validator = Validator::make($request->all(), [ + 'invoice_number' => 'required', + 'invoice_date' => 'required', + 'start_date' => 'required', + 'end_date' => 'required', + 'payments' => 'required', + 'invoice_payment_details' => 'required', + ], [ + 'invoice_number.required' => trans('Validation.required',['attribute' => 'Nomor Invoice']), + 'invoice_date.required' => trans('Validation.required',['attribute' => 'Tanggal Invoice']), + 'start_date.required' => trans('Validation.required',['attribute' => 'Tanggal Mulai']), + 'end_date.required' => trans('Validation.required',['attribute' => 'Tanggal Akhir']), + 'payments.required' => trans('Validation.required',['attribute' => 'Jumlah Bayar']), + 'invoice_payment_details.required' => trans('Validation.required',['attribute' => 'Nomor Claim']), + ]); + if ($validator->fails()) + { + return response()->json(['message' => $validator->errors()], 400); + } + else + { + try { + DB::beginTransaction(); + $invoiceNumber = $request->invoice_number; + foreach ($request->payments as $valuePayments) { + if($request->param === 'Edit') + { + $invoiceNumber = DB::table('invoice_payments') + ->where('id', $valuePayments['id']) + ->value('invoice_number'); + } + // **Cek apakah invoice_number sudah ada di database** + $existingInvoice = DB::table('invoice_payments') + ->where('invoice_number', $invoiceNumber) + ->where('payment_number', $valuePayments['paymentNumber']) + // ->where('amount_paid', $this->normalizeCurrency($valuePayments['amount'])) + ->exists(); + + //Insert + if (!$existingInvoice) { + $lastInsertedId = DB::table('invoice_payments') + ->insertGetId([ + 'invoice_number' => $request->invoice_number, + 'payment_number' => $valuePayments['paymentNumber'], + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->start_date, + 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), + 'status' => 'submitted', + 'created_by' => auth()->user()->id, + 'created_at' => date('Y-m-d H:i:s'), + ]); + + foreach ($request->invoice_payment_details as $value) + { + // **Cek apakah claim_request_id sudah ada untuk invoice_payment_id ini** + $existingClaim = DB::table('invoice_payment_details') + ->where('claim_request_id', $value) + ->exists(); + + if (!$existingClaim) { + DB::table('invoice_payment_details') + ->insert([ + 'invoice_payment_id' => $lastInsertedId, + 'claim_request_id' => $value, + 'created_by' => auth()->user()->id, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + } + if (!empty($valuePayments['files']) && is_array($valuePayments['files'])) { + foreach ($valuePayments['files'] as $file) { + $pathFile = File::storeFile('files-proof-payment', $lastInsertedId, $file); + File::updateOrCreate([ + 'fileable_type' => 'App\Models\InvoicePayment', + 'fileable_id' => $lastInsertedId, + 'type' => 'files-proof-payment', + 'name' => File::getFileName('files-proof-payment', $lastInsertedId, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + } + else + { + //Edit + $invoicePaymentId = DB::table('invoice_payments') + ->where('invoice_number', $invoiceNumber) + ->where('payment_number', $valuePayments['paymentNumber']) + ->value('id'); + + DB::table('invoice_payments') + ->where('id', $invoicePaymentId) + ->update([ + 'invoice_number' => $request->invoice_number, + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->end_date, + 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), + 'updated_by' => auth()->user()->id, + 'updated_at' => date('Y-m-d H:i:s'), + ]); + + $existingClaims = DB::table('invoice_payment_details') + ->where('invoice_payment_id', $invoicePaymentId) + ->pluck('claim_request_id') + ->toArray(); + + $newClaims = $request->invoice_payment_details; + + // Data yang mau di-insert + $claimsToInsert = array_diff($newClaims, $existingClaims); + foreach ($claimsToInsert as $claim) { + DB::table('invoice_payment_details')->insert([ + 'invoice_payment_id' => $invoicePaymentId, + 'claim_request_id' => $claim, + 'updated_by' => auth()->user()->id, + 'updated_at' => now(), + ]); + } + + // Data yang mau di-delete (tidak ada di data baru) + $claimsToDelete = array_diff($existingClaims, $newClaims); + if (!empty($claimsToDelete)) { + DB::table('invoice_payment_details') + ->where('invoice_payment_id', $invoicePaymentId) + ->whereIn('claim_request_id', $claimsToDelete) + ->update([ + 'deleted_by' => auth()->user()->id, + 'deleted_at' => now(), + ]); + } + + // Handle existing files + $existingFiles = DB::table('files') + ->where('files.fileable_id', $invoicePaymentId) + + ->where('files.type', 'files-proof-payment') + ->pluck('id') + ->toArray(); + + $newFiles = []; + if (!empty($valuePayments['existingFiles']) && is_array($valuePayments['existingFiles'])) { + $newFiles = array_column($valuePayments['existingFiles'], 'file_id'); + } + + + $filesToDelete = array_diff($existingFiles, $newFiles); + + if (!empty($filesToDelete)) { + DB::table('files') + ->whereIn('id', $filesToDelete) + ->update([ + 'deleted_by' => auth()->user()->id, + 'deleted_at' => now(), + ]); + } + + + //File New + if (!empty($valuePayments['files']) && is_array($valuePayments['files'])) { + foreach ($valuePayments['files'] as $file) { + $pathFile = File::storeFile('files-proof-payment', $invoicePaymentId, $file); + File::updateOrCreate([ + 'fileable_type' => 'App\Models\InvoicePayment', + 'fileable_id' => $invoicePaymentId, + 'type' => 'files-proof-payment', + 'name' => File::getFileName('files-proof-payment', $invoicePaymentId, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + } + } + + + DB::commit(); + + return response()->json(['message' => 'Data berhasil disimpan'], 200); + } + catch (\Exception $e) { + DB::rollback(); + return response()->json(['message' => $e->getMessage()], 500); + } + } + } + + function normalizeCurrency($amount) { + return (int) preg_replace('/\D/', '', $amount); + } + + public function submitStatus(Request $request) + { + $request->validate([ + 'valueStatus' => 'required|in:submitted,accepted,partial_paid,paid,decline', + 'valueID' => 'required|integer|exists:invoice_payments,id', + ]); + + $update = DB::table('invoice_payments') + ->where('id', $request->valueID) + ->update([ + 'status' => $request->valueStatus, + 'updated_at' => now(), + 'updated_by' => auth()->user()->id, + ]); + + if (!$update) { + return response()->json(['message' => 'Data tidak ditemukan atau tidak diperbarui'], 404); + } + + return response()->json(['message' => 'Status berhasil diperbarui']); + } + + public function export(Request $request) + { + $start_date = $request->input('start_date') ? $request->input('start_date') : 'all'; + $end_date = $request->input('end_date') ? $request->input('end_date') : 'all'; + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile(public_path('files/Report-Data-Invoice-Management-'.$start_date.'-'.$end_date.'.xlsx')); + $header = [ + 'No', + 'Nomor Invoice', + 'Pembayaran Ke', + 'Tanggal Invoice', + 'Start Date', + 'End Date', + 'Nominal Pembayaran', + 'Created At', + 'Updated At', + ]; + $style = (new StyleBuilder()) + ->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + + $headerRow = WriterEntityFactory::createRowFromArray($header, $style); + $writer->addRow($headerRow); + // ============================ + $results = DB::table('invoice_payments') + // ->leftJoin('member_plans', 'member_plans.member_id', '=', 'members.id') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('invoice_payments.invoice_number', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('claim_requests.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('claim_requests.created_at', '<=', $end_date); + }); + }) + ->select( + 'invoice_payments.*') + ->get(); + $no=0; + $gr_total = 0; + $header = [ + 'No', + 'Nomor Invoice', + 'Pembayaran Ke', + 'Tanggal Invoice', + 'Start Date', + 'End Date', + 'Nominal Pembayaran', + 'Created At', + 'Updated At', + ]; + foreach($results as $item) + { + $gr_total += $item->amount_paid; + $no++; + $rowData = [ + $no, + $item->invoice_number, + $item->payment_number, + $item->invoice_date, + $item->start_date, + $item->end_date, + $item->amount_paid, + $item->created_at, + $item->updated_at + ]; + $style = (new StyleBuilder()) + //->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + $row = WriterEntityFactory::createRowFromArray($rowData, $style); + $writer->addRow($row); + } + $footer = [ + 'Grand Total', + '', + '', + '', + '', + '', + $gr_total, + '', + '', + ]; + $style = (new StyleBuilder()) + ->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + + $footerRow = WriterEntityFactory::createRowFromArray($footer, $style); + $writer->addRow($footerRow); + + $writer->close(); + + return Helper::responseJson([ + 'file_name' => 'Report-Data-Invoice-Management-'. $start_date.'-'.$end_date, + "file_url" => url('files/Report-Data-Invoice-Management-'. $start_date.'-'.$end_date.'.xlsx') + ]); + } + +} diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 61abb29d..0f6fde0a 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -8,6 +8,7 @@ use Modules\Internal\Http\Controllers\Api\BenefitController; use Modules\Internal\Http\Controllers\Api\CityController; use Modules\Internal\Http\Controllers\Api\ClaimController; use Modules\Internal\Http\Controllers\Api\ClaimRequestController; +use Modules\Internal\Http\Controllers\Api\InvoicePaymentController; use Modules\Internal\Http\Controllers\Api\KatalogDokterController; use Modules\Internal\Http\Controllers\Api\RequestLogController; use Modules\Internal\Http\Controllers\Api\RequestLogBenefitController; @@ -86,7 +87,7 @@ Route::prefix('internal')->group(function () { Route::get('drugs', [AutocompleteController::class, 'drugList']); Route::get('units', [AutocompleteController::class, 'unitList']); - + Route::get('signa', [AutocompleteController::class, 'signaList']); Route::post('signa-add', [AutocompleteController::class, 'signaAdd']); @@ -268,7 +269,15 @@ Route::prefix('internal')->group(function () { Route::post('claims/{claim_id}/encounters/{encounter_id}/update', [ClaimEncounterController::class, 'update']); Route::post('claims/{claim_id}/set-final-encounter', [ClaimEncounterController::class, 'setFinalEncounter']); + Route::post('invoice_payments/create', [InvoicePaymentController::class, 'create']); + Route::get('invoice_payments/list', [InvoicePaymentController::class, 'index']); + Route::post('invoice-payment/submit-status', [InvoicePaymentController::class, 'submitStatus']); + Route::get('invoice-payment/detail/{id}', [InvoicePaymentController::class, 'detail']); + Route::get('invoice-payment/claim', [InvoicePaymentController::class, 'claim']); + Route::get('invoice-payment/export', [InvoicePaymentController::class, 'export']); + Route::get('claims', [ClaimController::class, 'index']); + Route::post('claim-details', [ClaimController::class, 'getClaimDetails']); Route::get('claims-files-provider', [ClaimController::class, 'filesProvider']); Route::post('download-zip', [ClaimController::class, 'downloadZip']); Route::get('claims/download-template', [ClaimController::class, 'downloadTemplate']); diff --git a/app/Models/File.php b/app/Models/File.php index 9e43e0aa..6b029667 100755 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -53,6 +53,7 @@ class File extends Model 'docs' => 'docs/', 'additional-files' => 'additional-files/', 'chat' => 'chat/', + 'files-proof-payment' => 'files-proof-payment/', ]; public function fileable() diff --git a/database/migrations/2025_02_21_140114_create_invoice_payments_table.php b/database/migrations/2025_02_21_140114_create_invoice_payments_table.php new file mode 100644 index 00000000..eda3c236 --- /dev/null +++ b/database/migrations/2025_02_21_140114_create_invoice_payments_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('invoice_number'); + $table->string('payment_number'); + $table->date('invoice_date'); + $table->date('start_date'); + $table->date('end_date'); + $table->decimal('amount_paid', 15, 2); + $table->string('status')->comment('submitted = Pengajuan, accepted = Belum Dibayar, partial_paid = Bayar Sebagian, paid = Sudah Dibayar'); + $table->bigInteger('created_by'); + $table->bigInteger('updated_by'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invoice_payments'); + } +}; diff --git a/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php b/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php new file mode 100644 index 00000000..3248584d --- /dev/null +++ b/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('invoice_payment_id')->constrained('invoice_payments')->onDelete('cascade'); + $table->foreignId('claim_request_id')->constrained('claim_requests')->onDelete('cascade'); + $table->bigInteger('created_by'); + $table->bigInteger('updated_by'); + $table->bigInteger('deleted_by'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invoice_payment_details'); + } +}; diff --git a/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx new file mode 100644 index 00000000..2e7beccc --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx @@ -0,0 +1,1573 @@ +// @mui +import { + Box, + Grid, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TableContainer, + Paper, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Tooltip, + TableHead, + Checkbox, + InputAdornment, + TableSortLabel, + FormControl, + Divider, + ButtonBase + } from '@mui/material'; + import { visuallyHidden } from '@mui/utils'; + import { fPostFormat } from '@/utils/formatTime'; + import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; + +import { makeFormData } from '@/utils/jsonToFormData'; + +import { fNumber } from '@/utils/formatNumber'; + + import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; + import { fDateOnly } from '@/utils/formatTime'; + +import { format } from 'date-fns'; + + import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import AssessmentIcon from '@mui/icons-material/Assessment'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { Link, Navigate, useParams, useNavigate, useSearchParams } from 'react-router-dom'; + + import { LoadingButton } from '@mui/lab'; + // components + import axios from '../../utils/axios'; + import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data'; + import DataTable from '../../components/LaravelTable'; + import { fCurrency } from '../../utils/formatNumber'; + import EditRoundedIcon from '@mui/icons-material/EditRounded'; + import { Chip } from '@mui/material'; + import Iconify from '@/components/Iconify'; + import { enqueueSnackbar } from 'notistack'; + import { fDate, fDateTime } from '../../utils/formatTime'; + import { Claims } from '@/@types/claims'; + import Label from '@/components/Label'; + import { capitalizeFirstLetter } from '@/utils/formatString'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import Edit from '@mui/icons-material/Edit'; + import { Download } from '@mui/icons-material'; + import { Add, Search } from '@mui/icons-material'; + import Autocomplete from '@mui/material/Autocomplete'; + + import DownloadIcon from '@mui/icons-material/Download'; + + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import CheckCircleIcon from '@mui/icons-material/CheckCircle'; + + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + +import { DatePicker, MobileDatePicker } from '@mui/x-date-pickers'; + + + export default function List() { + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + + const [providers, setProviders] = useState(null); + // const [searchText, setSearchText] = useState(''); + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('created_at'); + const [perPage, setPerPage] = useState(0); + const [invoicePayment, setInvoicePayment] = useState([]); + const [invoicePaymentDetail, setInvoicePaymentDetail] = useState([]); + const [invoicePaymentFile, setInvoicePaymentFile] = useState([]); + + const { invoiceID } = useParams(); + + useEffect(() => { + if (!invoiceID) return; + axios + .get('invoice-payment/detail/'+invoiceID) + .then((response) => { + setInvoicePayment(response.data.invoice_payments); + setInvoicePaymentDetail(response.data.invoice_payment_details); + setInvoicePaymentFile(response.data.files); + }) + .catch((error) => { + console.error(error); + }); + }, [invoiceID]); + + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData.data + .filter(row => row.status === 'approved') + .map(row => row.id); + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleRowSelect = (id) => { + if (selectedRows.includes(id)) { + setSelectedRows(selectedRows.filter(rowId => rowId !== id)); + } else { + setSelectedRows([...selectedRows, id]); + } + }; + + const [searchParams, setSearchParams] = useSearchParams(); + const [startDate, setStartDate] = useState(null); + const [searchText, setSearchText] = useState(''); + const [endDate, setEndDate] = useState(null); + const navigate = useNavigate(); + const [dataProvider, setDataProvider] = useState(null); + + useEffect(() => { + if (startDate !== null || endDate !== null || dataProvider !== null + || order !== null || orderBy !== null || perPage !== 0) { + loadDataTableData(); + getProvider(); + } + }, [startDate, endDate, dataProvider, order, orderBy, perPage]); + + useEffect(() => { + if (dataProvider) { + const newParams = new URLSearchParams(searchParams); + newParams.set("provider_id", dataProvider); // Set query param + navigate(`invoice-payment/claim?${newParams.toString()}`, { replace: true }); // Update URL + } + }, [dataProvider, navigate, searchParams]); + + const [isLoading, setIsLoading] = useState(false); + const [isLoadingImport, setIsLoadingImport] = useState(false); + const handleExportReport = async () => { + + + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + + + var filter = Object.fromEntries([...searchParams.entries()]); + setIsLoading(true) + await axios + .get('/claims/export-claim-management',{ + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + orderBy: orderBy, + page: perPage, + } + }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + const handleGetData = (type :string) => { + axios.get(`claims/1/data-claim`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + }) + } + + useEffect(() => { + // Trigger First Search + // setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + const searchInput = useRef(null); + + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + loadDataTableData(); + }; + + + + useEffect(() => { + // Trigger First Search + //setSearchText(searchText); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + // const handleClick = () => { + + // } + + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('invoice-payment/claim', { + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + param: invoiceID ? 'Edit' : 'Add', + invoiceID: invoiceID, + orderBy: orderBy, + page: perPage, + } + }); + + setDataTableLoading(false); + + setDataTableData(response.data); + }; + const id = searchParams.get("provider_id"); + const provider_id = id ? parseInt(id, 10) : null; + const getProvider = async () => { + const response = await axios.get('/claims/get-provider'); + setProviders(response.data); + var id = response.data[0].id; + // if(provider_id) + // { + // id = provider_id; + // } + // setDataProvider(dataProvider); + + + + } + + // if(provider_id) { + // setDataProvider(provider_id); + // } + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + setPerPage(value); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [approve, setApprove] = useState(''); + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const handleChooseClaim = () => { + setOpenDialogSubmit(false); + handleGetClaimDetails(); + }; + + const handleSubmitData = () => { + setIsLoading(true) + + //approve or decline + if(noInvoice == '') + { + setIsLoading(false) + enqueueSnackbar('Mohon isi nomor invoice terlebih dahulu', { variant: 'warning' }); + return false; + } + const formData = makeFormData({ + invoice_number: noInvoice, + invoice_payment_details: selectedRows, + invoice_date: fPostFormat(tglInvoice), + start_date: fPostFormat(tglMulai), + end_date: fPostFormat(tglAkhir), + payments: payments, + param: invoiceID ? 'Edit' : 'Add', + invoiceID: invoiceID, + + }); + axios + .post('invoice_payments/create', formData) + .then((response) => { + setIsLoading(false) + enqueueSnackbar(response.data.message, { variant: 'success' }); + setTimeout(() => { + window.location.reload(); + }, 1000); + }) + .catch(({ response }) => { + setIsLoading(false) + enqueueSnackbar(response.data.message.invoice_payment_details[0], { variant: 'error' }); + }) + .then(() => { + }); + // console.log(formData); + // if (!reasonDecline && approve == 'decline') { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // Promise.all(selectedRows.map(send_bulk)) + // .then(() => { + // enqueueSnackbar('All requests processed successfully', { variant: 'success' }); + // setOpenDialogSubmit(false); + // }) + // .catch((error) => { + // enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + }; + + function send_bulk(id) { + return true; + } + + + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importClaimManagement = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const [importResult, setImportResult] = useState(null); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleImportButton = () => { + if (importClaimManagement?.current) { + handleClose(); + importClaimManagement.current ? importClaimManagement.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + const handleCancelImportButton = () => { + if(importClaimManagement.current) + { + importClaimManagement.current.value = ''; + importClaimManagement.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(importClaimManagement.current && importClaimManagement.current.files) + { + if (importClaimManagement.current?.files.length) { + const formData = new FormData(); + formData.append('file', importClaimManagement.current?.files[0]); + setImportLoading(true); + axios + .post('claims/import', formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + setImportLoading(false); + enqueueSnackbar('Success Import Claim Managemenet', { variant: 'success' }); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + } + }; + const handleGetTemplate = () => { + axios.get('claims/download-template').then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }); + + + }; + + const handleExportReportFiled = async () => { + + await axios + .post('claims/exportFiled', { params: importResult?.data.result_rows }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + // useEffect(() => { + // loadDataTableData(); + // getProvider(); + // }, []); + + const headStyle = { + fontWeight: 'bold', + }; + const headCells = [ + { + id: 'code', + align: 'left', + label: 'Code / Code LOG', + isSort: true, + }, + { + id: 'name', + align: 'left', + label: 'Name', + isSort: false, + }, + + { + id: 'member_id', + align: 'left', + label: 'Member ID', + isSort: false, + }, + { + id: 'created_at', + align: 'left', + label: 'Date Submission', + isSort: true, + }, + { + id: 'plan_code', + align: 'left', + label: 'Plan ID', + isSort: true, + }, + { + id: 'service_code', + align: 'left', + label: 'Service', + isSort: false, + }, + { + id: 'corporate_policies', + align: 'left', + label: 'Policy Number', + isSort: true, + }, + { + id: 'provider', + align: 'left', + label: 'Provider', + isSort: false, + }, + { + id: 'tot_bill', + align: 'left', + label: 'Total Billing', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Status', + isSort: false, + }, + { + id: 'action', + align: 'left', + label: '', + isSort: false, + }, + ]; + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + }; + // Called on every row to map the data to the columns + function createData(data: Claims): Claims { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType, isSelected: boolean, onSelect: (id: string) => void }) { + const { row, isSelected, onSelect } = props; + // Memperbaiki destrukturisasi props + + const handleRowCheckboxChange = () => { + onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik + }; + + const [open, setOpen] = React.useState(false); + + const test = 1000; + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */} + + {row?.status == 'approved' ? ( + + ):''} + + {row?.code} / {row.code_log} + {/* {row.code} */} + {row?.name} + {row?.member_id} + {row?.created_at ? fDateTime(row?.created_at) : ''} + {row?.plan_code} + {row?.service_code} + {row?.corporate_policies} + {row?.provider} + Rp. {row?.tot_bill?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")} + + {row.status == 'draft' && ()} + {row.status == 'requested' && ()} + {row.status == 'received' && ()} + {row.status == 'approved' && ()} + {row.status == 'postpone' && ()} + {row.status == 'paid' && ()} + {row.status == 'declined' && ()} + + + + + navigate('/claims/detail/'+row.id_log+'/'+row.id+'') }> + + Detail + + + } /> + + + + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + + + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {selectedRows.length > 0 ? ( + <> + + + + {selectedRows.length > 0 ? selectedRows.length : '0'}  Selected + + + + + + + {/* */} + + + {/* */} + + + + ) : ( + <> + + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + )} + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + Data tidak ditemukan, silahkan pilih provider lainnya + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + // Data contoh + + const [dataLogs, setDataLogs] = useState([]); + +const handleGetClaimDetails = async () => { + if (!selectedRows.length) { + console.warn("No selected rows"); + setDataLogs([]); + return; + } + + try { + const response = await axios.post('/claim-details', { ids: selectedRows }); + const claimDetails = response.data; + + // Map the response data into the new format and set state + const logs = claimDetails.map((item) => { + const formatDate = (dateString) => { + if (dateString && !isNaN(new Date(dateString).getTime())) { + return fDateTime(dateString.split(" ")[0]); + } + return '-'; + }; + + return { + code: item.code_log, + dateSubmission: formatDate(item.submission_date), + dateAdmission: formatDate(item.addmission_date), + dateDischarge: formatDate(item.discharge_date), + provider: item.provider, + memberName: item.name, + policyNumber: item.corporate_policies, + totalBilling: item.tot_bill || 0, + }; + }); + + // Update state + setDataLogs(logs); + + console.log("Claim Details:", logs); + } catch (error) { + console.error("Error fetching claim details:", error); + } +}; + + + + // Hitung Grand Total + const grandTotal = dataLogs.reduce((sum, item) => sum + item.totalBilling, 0); + const [fileBukti, setFileBukti] = useState([]); + const removeBuktiFiles = (filesState:any, index:any) => { + setFileBukti( + filesState.filter((file:any, fileIndex:any) => { + return fileIndex != index; + }) + ); + }; +// const fileBuktiInput = useRef(null); + const handleBuktiInputChange = (event:any) => { + if (event.target.files[0]) { + setFileBukti([...fileBukti, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + + const [jmlBayar, setJmlBayar] = useState(""); + const [noInvoice, setNoInvoice] = useState(''); + + useEffect(() => { + if (invoicePayment[0]?.invoice_number) { + setNoInvoice(invoicePayment[0].invoice_number); + } + }, [invoicePayment]); + + useEffect(() => { + // Ambil semua id dari invoicePaymentDetail + const ids = invoicePaymentDetail.map(item => item.id); + setSelectedRows(ids); + }, [invoicePaymentDetail]); + + // Gunakan useEffect lain untuk menjalankan handleGetClaimDetails setelah selectedRows berubah + useEffect(() => { + if (selectedRows.length > 0) { + handleGetClaimDetails(); + } + }, [selectedRows]); + + + + + const [noTagihan, setNoTagihan] = useState(""); + + const [tglBayar, setTglBayar] = useState(format(new Date(), "yyyy MMM d HH:mm:ss")); + const [tglMulai, setTglMulai] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.start_date) { + setTglMulai(format(new Date(invoicePayment[0].start_date), "yyyy MMM d")); + } + }, [invoicePayment]); + const [tglInvoice, setTglInvoice] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.invoice_date) { + setTglInvoice(format(new Date(invoicePayment[0].invoice_date), "yyyy MMM d")); + } + }, [invoicePayment]); + const [tglAkhir, setTglAkhir] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.end_date) { + setTglAkhir(format(new Date(invoicePayment[0].end_date), "yyyy MMM d")); + } + }, [invoicePayment]); + + const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], paymentNumber: 1 }]); + const fileInputRefs = useRef({}); + + useEffect(() => { + if (invoicePaymentFile?.length) { + const mappedPayments = invoicePaymentFile.map((payment) => ({ + id: payment.id, + amount: fCurrency(parseFloat(payment.amount_paid)), + existingFiles: Array.isArray(payment.files) ? payment.files : [], + files: [], + paymentNumber: payment.payment_number + })); + setPayments(mappedPayments); + } + }, [invoicePaymentFile]); + + + const handleAddPayment = () => { + const newId = Date.now(); + setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], paymentNumber: payments.length + 1 }]); + fileInputRefs.current[newId] = null; + }; + + const handleAmountChange = (index, value) => { + const updatedPayments = [...payments]; + updatedPayments[index].amount = fCurrency(value); + setPayments(updatedPayments); + }; + + const handleFileChange = (index, event) => { + const newFiles = Array.from(event.target.files); + const updatedPayments = [...payments]; + updatedPayments[index].files = [...updatedPayments[index].files, ...newFiles]; + setPayments(updatedPayments); + }; + + const handleRemoveFile = (paymentIndex, fileIndex, isNew = false) => { + const updatedPayments = [...payments]; + if (isNew) { + updatedPayments[paymentIndex].files.splice(fileIndex, 1); + } else { + updatedPayments[paymentIndex].existingFiles.splice(fileIndex, 1); + } + setPayments(updatedPayments); + }; + + const handleRemovePayment = (index) => { + const updatedPayments = [...payments]; + updatedPayments.splice(index, 1); + setPayments(updatedPayments); + }; + + const totalAmount = payments.reduce((sum, payment) => { + const num = parseFloat(payment.amount.replace(/[^\d]/g, "")) || 0; + return sum + num; + }, 0); + + + return ( + <> + + navigate(`/invoice-payment/`)} + sx={{ cursor: 'pointer' }} + /> + + { (invoiceID ? 'Edit Invoice' : 'Create Invoice')} + + + + + + + Tanggal Invoice + + + { + setTglInvoice(newValue); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + + + + Nomor Invoice * + setNoInvoice(event.target.value)} + /> + + {/* + Tanggal Bayar + + { + setTglBayar( (newValue)); + }} + inputFormat="dd-MM-yyyy HH:mm" + renderInput={(params) => } + /> + + */} + + Periode Pembayaran + + { + setTglMulai( (newValue)); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + +   + + { + setTglAkhir( (newValue)); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + + + + Claim + setOpenDialogSubmit(true)} + > + + { !selectedRows.length ? 'Add Claim' : 'Edit Claim'} + + + + + + + Code LOG + Invoice Provider + Date Submission + Date Admission + Date Discharge + Provider + Member Name + Policy Number + Total Billing + + + + {dataLogs.map((row, index) => ( + + {row.code} + {row.invoice_no} + {row.dateSubmission} + {row.dateAdmission} + {row.dateDischarge} + {row.provider} + {row.memberName} + {row.policyNumber} + {fCurrency(row.totalBilling)} + + ))} + {/* Grand Total */} + + Grand Total + {fCurrency(grandTotal)} + + +
+
+ + + Catatan Pembayaran + + + Add Payment + + + + {payments.map((payment, index) => ( + + + Pembayaran {payment.paymentNumber} + {payments.length > 1 && !invoiceID ? ( + handleRemovePayment(index)} sx={{ color: "darkred" }}> + + + ) : ''} + + + {/* Input Jumlah Bayar */} + + + Nominal Pembayaran + handleAmountChange(index, e.target.value)} + /> + + + {/* Input File Bukti */} + + Upload Bukti Pembayaran + + } spacing={1}> + {payment.existingFiles.map((file, fileIndex) => + file.original_name && ( // No need for ternary, just use short-circuiting + + {file.original_name} + handleRemoveFile(index, fileIndex)} sx={{ color: "darkred" }}> + + + + ) + )} + + {payment.files.map((file, fileIndex) => ( + + {file.name} + handleRemoveFile(index, fileIndex, true)} sx={{ color: "darkred" }}> + + + + ))} + + + fileInputRefs.current[payment.id]?.click()} + > + + + Tambah File + + + + (fileInputRefs.current[payment.id] = el)} + style={{ display: "none" }} + multiple + onChange={(e) => handleFileChange(index, e)} + accept="application/pdf,image/*" + /> + + + + + ))} + + Jumlah Tagihan + + {fCurrency(grandTotal)} + + + {/* Total Jumlah Bayar */} + + Total Dibayar + + {fCurrency(totalAmount.toString())} + + + + Sisa Pembayaran + + {fCurrency((grandTotal - totalAmount))} + + + + {/* Submit Button */} + } + loading={isLoading} + > + Submit Pembayaran ({fCurrency(totalAmount.toString())}) + + + + {/* + + Nomor LOG * + + {nomorLogs.map((log, index) => ( + + {log} + + ))} + + + + + + Nomor LOG * + + {nomorLogs.map((log, index) => ( + + {log} + + ))} + + + */} +
+ + + + + Daftar Claim + + + + + + + + + +
+ + + {!currentImportFileName && ( + <> + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search Code or Name', + }} + /> + + + + { + + // loadDataTableData(); + setStartDate(value); + }} + renderInput={(params) => } + /> + + + + { + setEndDate(value); + }} + renderInput={(params) => ( + + )} + /> + + + + { + providers && ( + option.name || ''} + value={providers.find((item) => item.id === dataProvider) || null} + onChange={(event, value) => { + if (value) { + setDataProvider(value.id); + } else { + setDataProvider(null); + } + }} + renderInput={(params) => ( + + )} + /> + ) + } + + + + } + sx={{ p: 1.8 }} + loading={isLoadingImport} + onClick={handleClick} + > + + Import + + + + + Import + + { + handleGetTemplate(); + }} + > + Download Template + + + + + } + sx={{ p: 1.8 }} + onClick={handleExportReport} + loading={isLoading} + > + + Export + + + + + + )} + {currentImportFileName && ( + + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed, + {/* {importResult.data.failed_rows.map((row, index) => ( + [Code={row.code ? row.code : 'Required'}] + ))} */} +  Report: +  Download Data Result Import + + + )} + +
+
+
+ + } + /> + +
+ + + + +
+
+ + + ); + } diff --git a/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx b/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx new file mode 100644 index 00000000..aa2c9a19 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx @@ -0,0 +1,323 @@ +import { + Container, + Grid, + Stack, + Typography, + Card, + TableRow, + Tab, + TableCell, + Collapse, + AccordionSummary, + AccordionDetails, + IconButton, + TextField + } from '@mui/material'; + import { fCurrency } from '../../utils/formatNumber'; + import { Table, TableBody, TableContainer, TableHead, Paper } from '@mui/material'; +// components +import Page from '@/components/Page'; +// utils +import useSettings from '@/hooks/useSettings'; +// react +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useEffect, useState, useRef, useMemo } from 'react'; +import axios from '@/utils/axios'; +// pages +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import { fDate, fDateTimesecond, fDateTime } from '@/utils/formatTime'; +import { Button } from '@mui/material'; +import Label from '@/components/Label'; +import { Box } from '@mui/system'; +import { Accordion } from '@mui/material'; +import { Delete, EditOutlined, ExpandMore } from '@mui/icons-material'; +import AddIcon from '@mui/icons-material/Add'; + +import MoreMenu from '@/components/MoreMenu'; +import { MenuItem } from '@mui/material'; +import { fNumber } from '@/utils/formatNumber'; +import palette from '@/theme/palette'; + +import CloseIcon from '@mui/icons-material/Close'; +import { enqueueSnackbar } from 'notistack'; +import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; +import { List, ListItem, Link, Divider } from '@mui/material'; + + + +// ---------------------------------------------------------------------- + +export default function Detail() { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [invoicePayment, setInvoicePayment] = useState([]); + const [invoicePaymentDetail, setInvoicePaymentDetail] = useState([]); + const [invoicePaymentFile, setInvoicePaymentFile] = useState([]); + const [statusClaim, setStatusClaim] = useState(''); + const [dataMember, setDataMember] = useState(''); + + + const { id } = useParams(); + + useEffect(() => { + axios + .get('invoice-payment/detail/'+id) + .then((response) => { + setInvoicePayment(response.data.invoice_payments); + setInvoicePaymentDetail(response.data.invoice_payment_details); + setInvoicePaymentFile(response.data.files); + }) + .catch((error) => { + console.error(error); + }); + }, [id]); + + console.log(invoicePayment); + console.log(invoicePaymentDetail); + console.log(invoicePaymentFile); + + const style1 = { + color: '#919EAB', + width: '30%' + } + const style3 = { + color: '#919EAB', + width: '35%' + } + const style2 = { + width: '70%' + } + const marginBottom1 = { + marginBottom: 1, + } + const marginBottom2 = { + marginBottom: 2, + } + + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + const [decline, setDeclaine] = useState(''); + + // const handleSubmitData = () => { + // //approve or decline + // if(!reasonDecline && approve == 'decline') + // { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // axios + // .post('claims/'+id_claim+'/'+approve, {reasonDecline:reasonDecline}) + // .then((response) => { + // enqueueSnackbar('Success '+toTitleCase(approve)+' Claim Request', { variant: 'success' }); + // setOpenDialogSubmit(false); + // // window.location.reload(); + + // }) + // .catch(({ response }) => { + // enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + + // setTimeout(() => + // { + // window.location.reload(); + // }, 5000); + + // }; + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const [openDialogEditDetail, setDialogDEditDetail] = useState(false); + const [openDialogBenefit, setDialogBenefit] = useState(false); + const [openDialogMedicine, setDialogMedicine] = useState(false); + + // Handel Delete Detail Benefit + const [idBenefitData, setIdBenefitData] = useState(); + const [openDialogDeleteBenefit, setDialogDeleteBenefit] = useState(false) + + const [idMedicineData, setIdMedicineData] = useState(); + const [openDialogDeleteMedicine, setDialogDeleteMedicine] = useState(false) + + const [approve, setApprove] = useState('') + + // Handle Edit Detail Benefit + const [openDialogEditBenefit, setDialogEditBenefit] = useState(false) + const [BenefitConfigurationData, setBenefitConfigurationData] = useState(); + + // Buat total data + + + // Handle Delete File LOG + const [pathFile, setPathFile] = useState('') + const [dialogDeleteFIleLog, setDialogDeleteFileLog] = useState(false) + + // Handle Upload File LOG + const [dialogUploadFileLog, setDialogUploadFileLog] = useState(false) + + const totalAmount = invoicePaymentFile.reduce((sum, payment) => { + const num = parseFloat(payment.amount_paid) || 0; + return parseFloat(sum) + parseFloat(num); + }, 0); + const grandTotal = invoicePaymentDetail.reduce((sum, item) => sum + parseFloat(item.tot_bill ? item.tot_bill : 0), 0); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + + {/* {invoicePayment.length > 0 ? invoicePayment[0].invoice_number : 'Detail'} */} + Detail Invoice + + + + {/* Detail */} + + + + + + Detail + + + + + Tanggal Invoice + {invoicePayment.length > 0 ? fDate(invoicePayment[0].invoice_date) : '-'} + + + Nomor Invoice + {invoicePayment.length > 0 ? invoicePayment[0].invoice_number : '-'} + + + Periode Pembayaran + {invoicePayment.length > 0 ? fDate(invoicePayment[0].start_date) : '-'}-{invoicePayment.length > 0 ? fDate(invoicePayment[0].end_date) : '-'} + + + + + + {/* Benefit */} + + + + Claim + + + + + + Code Claim/Code Log + Invoice No + Name + Member ID + Policy Number + Provider + Total Bill + + + + {invoicePaymentDetail.map((detail) => ( + + {detail.code} / {detail.code_log} + {detail.invoice_no || '-'} + {detail.name} + {detail.member_id} + {detail.corporate_policies} + {detail.provider} + {fCurrency(detail.tot_bill)} + + ))} + +
+
+
+ + + +
+ + {/* Catatan Pembayaran */} + + + + Catatan Pembayaran + + + {invoicePaymentFile.map((file, index) => ( + + + Pembayaran {file.payment_number} + + + {/* Input Jumlah Bayar */} + + + Nominal Pembayaran + {fCurrency(parseFloat(file.amount_paid))} + + + {/* Input File Bukti */} + + Bukti Pembayaran + + } spacing={1}> + {file.files.map((file1, fileIndex) => ( + + + {file1.original_name ? file1.original_name : '-'} + + + ))} + + + + + + ))} + + Jumlah Tagihan + + {fCurrency(grandTotal)} + + + {/* Total Jumlah Bayar */} + + Total Dibayar + + {fCurrency(totalAmount.toString())} + + + + Sisa Pembayaran + + {fCurrency((grandTotal - totalAmount))} + + + + +
+
+
+ ); +} diff --git a/frontend/dashboard/src/pages/InvoicePayment/Index.tsx b/frontend/dashboard/src/pages/InvoicePayment/Index.tsx new file mode 100644 index 00000000..b3fc9cc7 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +import Page from "../../components/Page"; +import List from "./List"; + + + +export default function Invoice() { + + const pageTitle = 'Invoice Management'; + return ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/dashboard/src/pages/InvoicePayment/List.tsx b/frontend/dashboard/src/pages/InvoicePayment/List.tsx new file mode 100644 index 00000000..8074f438 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/List.tsx @@ -0,0 +1,1063 @@ +// @mui +import { + Box, + Grid, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Tooltip, + TableHead, + Checkbox, + InputAdornment, + TableSortLabel, + FormControl + } from '@mui/material'; +import { ClearOutlined, LoopOutlined } from "@mui/icons-material"; + import { visuallyHidden } from '@mui/utils'; + + import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; + import { fDateOnly } from '@/utils/formatTime'; + + import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import AssessmentIcon from '@mui/icons-material/Assessment'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom'; + + import { LoadingButton } from '@mui/lab'; + // components + import axios from '../../utils/axios'; + import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data'; + import DataTable from '../../components/LaravelTable'; + import { fCurrency } from '../../utils/formatNumber'; + import EditRoundedIcon from '@mui/icons-material/EditRounded'; + import { Chip } from '@mui/material'; + import Iconify from '@/components/Iconify'; + import { enqueueSnackbar } from 'notistack'; + import { fDate, fDateTime } from '../../utils/formatTime'; + import { Claims } from '@/@types/claims'; + import Label from '@/components/Label'; + import { capitalizeFirstLetter } from '@/utils/formatString'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import Edit from '@mui/icons-material/Edit'; + import { Download } from '@mui/icons-material'; + import { Add, Search } from '@mui/icons-material'; + import Autocomplete from '@mui/material/Autocomplete'; + + import DownloadIcon from '@mui/icons-material/Download'; + + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import CheckCircleIcon from '@mui/icons-material/CheckCircle'; + + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; + + + export default function List() { + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const [providers, setProviders] = useState(null); + const [filterData, setStatusData] = useState([]); + // const [searchText, setSearchText] = useState(''); + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('created_at'); + const [perPage, setPerPage] = useState(0); + + const handleChange = (event, newValue) => { + // Jika newValue tidak undefined, atur nilai dataProvider + if (newValue !== undefined) { + setDataProvider(newValue.service_code); + } else { + // Jika tidak ada yang dipilih, set dataProvider menjadi string kosong + setDataProvider(null); + } + }; + + + + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData.data + .filter(row => row.status === 'received') // Memfilter baris dengan status 'requested' + .map(row => row.id); // Mengambil hanya ID dari baris-baris yang memenuhi kondisi + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleRowSelect = (id) => { + if (selectedRows.includes(id)) { + setSelectedRows(selectedRows.filter(rowId => rowId !== id)); + } else { + setSelectedRows([...selectedRows, id]); + } + }; + + const [searchParams, setSearchParams] = useSearchParams(); + const [startDate, setStartDate] = useState(null); + const [searchText, setSearchText] = useState(''); + const [endDate, setEndDate] = useState(null); + const navigate = useNavigate(); + const [dataProvider, setDataProvider] = useState(null); + const [dataStatus, setDataStatus] = useState(null); + + useEffect(() => { + if (startDate !== null || endDate !== null || dataProvider !== null || dataStatus !== null + || order !== null || orderBy !== null || perPage !== 0) { + loadDataTableData(); + getProvider(); + } + }, [startDate, endDate, dataProvider, dataStatus, order, orderBy, perPage]); + + const [isLoading, setIsLoading] = useState(false); + const [isLoadingImport, setIsLoadingImport] = useState(false); + const handleExportReport = async () => { + + + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + + + var filter = Object.fromEntries([...searchParams.entries()]); + setIsLoading(true) + await axios + .get('/invoice-payment/export',{ + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + status: dataStatus, + order: order, + orderBy: orderBy, + page: perPage, + } + }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + const handleGetData = (type :string) => { + axios.get(`claims/1/data-claim`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + }) + } + + useEffect(() => { + // Trigger First Search + // setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + const searchInput = useRef(null); + + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + loadDataTableData(); + }; + + + + useEffect(() => { + // Trigger First Search + //setSearchText(searchText); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + // const handleClick = () => { + + // } + + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('invoice_payments/list', { + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + status: dataStatus, + order: order, + orderBy: orderBy, + page: perPage, + } + }); + + setDataTableLoading(false); + + setDataTableData(response.data); + + const status: any = [ + { "id": "submitted", "name": "Pengajuan" }, // Sudah di-submit + { "id": "accepted", "name": "Belum Dibayar" }, // Sudah diterima keuangan + { "id": "partial_paid", "name": "Bayar Sebagian" }, // Sudah dibayar, namun belum lunas + { "id": "paid", "name": "Sudah Dibayar" } // Lunas + ]; + setStatusData(status); + }; + + const getProvider = async () => { + const response = await axios.get('/claims/get-provider'); + setProviders(response.data) + } + + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + setPerPage(value); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [approve, setApprove] = useState(''); + const [valueStatus, setValueStatus] = useState(''); + const [valueID, setValueID] = useState(); + const [alertMessage, setAlertMessage] = useState(''); + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const handleSubmitData = async () => { + try { + + const payload = { + valueStatus: valueStatus, + valueID: valueID + }; + + const response = await axios.post('/invoice-payment/submit-status', payload); + + if (response.status === 200) { + enqueueSnackbar('Status berhasil diperbarui', { variant: 'success' }); + handleCloseDialogSubmit(); + loadDataTableData(); + } else { + enqueueSnackbar('Gagal memperbarui status', { variant: 'error' }); + } + } catch (error) { + enqueueSnackbar('Terjadi kesalahan, coba lagi nanti', { variant: 'error' }); + console.error('Error:', error); + } + }; + + // const handleSubmitData = () => { + // //approve or decline + // if (!reasonDecline && approve == 'decline') { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // Promise.all(selectedRows.map(send_bulk)) + // .then(() => { + // enqueueSnackbar('Status Berhasil diubah', { variant: 'success' }); + // setOpenDialogSubmit(false);loadDataTableData(); + // }) + // .catch((error) => { + // enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + // }; + + // function send_bulk(id) { + // return axios.post(`claims/${id}/${approve}`, { reasonDecline: reasonDecline }); + // } + + + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importClaimManagement = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const [importResult, setImportResult] = useState(null); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleImportButton = () => { + if (importClaimManagement?.current) { + handleClose(); + importClaimManagement.current ? importClaimManagement.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + const handleCancelImportButton = () => { + if(importClaimManagement.current) + { + importClaimManagement.current.value = ''; + importClaimManagement.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(importClaimManagement.current && importClaimManagement.current.files) + { + if (importClaimManagement.current?.files.length) { + const formData = new FormData(); + formData.append('file', importClaimManagement.current?.files[0]); + setImportLoading(true); + axios + .post('claims/import', formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + setImportLoading(false); + enqueueSnackbar('Success Import Claim Managemenet', { variant: 'success' }); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + } + }; + const handleGetTemplate = () => { + axios.get('claims/download-template').then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }); + + + }; + + const handleExportReportFiled = async () => { + + await axios + .post('claims/exportFiled', { params: importResult?.data.result_rows }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + // useEffect(() => { + // loadDataTableData(); + // getProvider(); + // }, []); + + const headStyle = { + fontWeight: 'bold', + }; + const headCells = [ + { + id: 'invoice_number', + align: 'left', + label: 'No Invoice', + isSort: true, + }, + { + id: 'created_at', + align: 'left', + label: 'Date of Submission', + isSort: false, + }, + + { + id: 'periode', + align: 'left', + label: 'Periode Pembayaran', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Status', + isSort: true, + }, + { + id: 'action', + align: 'left', + label: '', + isSort: false, + }, + ]; + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + }; + // Called on every row to map the data to the columns + function createData(data: Claims): Claims { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType, isSelected: boolean, onSelect: (id: string) => void }) { + const { row, isSelected, onSelect } = props; + // Memperbaiki destrukturisasi props + + const handleRowCheckboxChange = () => { + onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik + }; + + const [open, setOpen] = React.useState(false); + + const test = 1000; + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */} + {row.invoice_number} + {/* {row.code} */} + {row?.created_at ? fDateTime(row?.created_at) : ''} + {row?.start_date ? fDate(row?.start_date) : ''} - {row?.end_date ? fDate(row?.end_date) : ''} + + {row.status === 'submitted' && ( + + )} + {row.status === 'accepted' && ( + + )} + {row.status === 'partial_paid' && ( + + )} + {row.status === 'paid' && ( + + )} + + + + + + navigate('/invoice-payment/detail/'+row.id+'') }> + + Detail + + navigate('/invoice-payment/edit/'+row.id+'') }> + + Edit + + {row.status !== 'paid' ? ( + { + setOpenDialogSubmit(true); + setApprove('approve'); + setValueID(row.id); + let message = ''; + if (row.status === 'submitted') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Pengajuan menjadi Belum Bayar?'; + setValueStatus('accepted'); + } else if (row.status === 'accepted') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Belum Bayar menjadi Bayar Sebagian?'; + setValueStatus('partial_paid'); + } else if (row.status === 'partial_paid') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Bayar Sebagian menjadi Sudah Dibayar?'; + setValueStatus('paid'); + } else if (row.status === 'paid') { + message = 'Lunas, Terima Kasih!'; + } + + if (message) { + setAlertMessage(message); + } + + }}> + + Update Status + + + ):''} + + + } /> + + + + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {selectedRows.length > 0 ? ( + <> + + + + {selectedRows.length > 0 ? selectedRows.length : '0'}  Selected + + + + + + + + + + + + + + ) : ( + <> + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + )} + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + Tidak Ada Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + return ( + + + +
+ + + {!currentImportFileName && ( + <> + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search Code or Name', + }} + /> + + + + { + + // loadDataTableData(); + setStartDate(value); + }} + renderInput={(params) => } + /> + + + + { + setEndDate(value); + }} + renderInput={(params) => ( + + )} + /> + + + + { + // providers && ( + // option.name || ''} + // value={providers.find((item) => item.id === dataProvider) || null} + // onChange={(event, value) => { + // if (value) { + // setDataProvider(value.id); + // } else { + // setDataProvider(null); + // } + // }} + // renderInput={(params) => ( + // + // )} + // /> + // ) + filterData && ( + option.name || ''} + value={filterData.find((item) => item.id === dataStatus) || null} + onChange={(event, value) => { + if (value) { + setDataStatus(value.id); + } else { + setDataStatus(null); + } + }} + renderInput={(params) => ( + + )} + /> + ) + } + + + + } + sx={{ p: 1.8 }} + onClick={handleExportReport} + loading={isLoading} + > + + Export + + + + + + + )} + {currentImportFileName && ( + + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed, + {/* {importResult.data.failed_rows.map((row, index) => ( + [Code={row.code ? row.code : 'Required'}] + ))} */} +  Report: +  Download Data Result Import + + + )} + +
+
+
+ + } + /> + + + + + Confirmation + + + + + + + + + + {alertMessage} + {approve == "decline" ? ( + + + + ): ''} + + + + + + + +
+ ); + } diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index bd69b2ce..a44879f0 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -480,6 +480,22 @@ export default function Router() { path: 'report/rujukan', element: , }, + { + path: 'invoice-payment', + element: , + }, + { + path: 'invoice-payment/create', + element: , + }, + { + path: 'invoice-payment/detail/:id', + element: , + }, + { + path: 'invoice-payment/edit/:invoiceID', + element: , + }, { path: 'claims', element: , @@ -780,6 +796,13 @@ const CorporateHistories = Loadable( const Profile = Loadable(lazy(() => import('../pages/Profile/Index'))); +//Invoice_Payments +const InvoicePayments = Loadable(lazy(() => import('../pages/InvoicePayment/Index'))); +const CreateInvoicePayments = Loadable(lazy(() => import('../pages/InvoicePayment/CreateInvoice'))); +const InvoicePaymentsDetail = Loadable(lazy(() => import('../pages/InvoicePayment/Detail'))); +const InvoicePaymentsEdit = Loadable(lazy(() => import('../pages/InvoicePayment/CreateInvoice'))); + + const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate'))); const ClaimsDetail = Loadable(lazy(() => import('../pages/Claims/Detail')));