Export Excel Invoice & Imporve Invoice

This commit is contained in:
ivan-sim
2025-12-18 09:53:01 +07:00
parent 9976e8e438
commit e178682fcf
13 changed files with 622 additions and 80 deletions

View File

@@ -42,11 +42,15 @@ class RequestLogController extends Controller
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'member_id' => 'required', 'member_id' => 'required',
'service_code' => 'required', 'service_code' => 'required',
'submission_date' => 'required' 'submission_date' => 'required',
'specialities_id' => 'required',
'dppj' => 'required',
], [ ], [
'member_id.required' => trans('Validation.required',['attribute' => 'Member ID']), 'member_id.required' => trans('Validation.required',['attribute' => 'Member ID']),
'service_code.required' => trans('Validation.required',['attribute' => 'Service Code']), 'service_code.required' => trans('Validation.required',['attribute' => 'Service Code']),
'submission_date.required' => trans('Validation.required',['attribute' => 'Submission Date']), 'submission_date.required' => trans('Validation.required',['attribute' => 'Submission Date']),
'specialities_id.required' => trans('Validation.required',['attribute' => 'Specialities']),
'dppj.required' => trans('Validation.required',['attribute' => 'DPJP']),
]); ]);
if(!empty($request->organization_id)) if(!empty($request->organization_id))
{ {
@@ -54,12 +58,16 @@ class RequestLogController extends Controller
'organization_id' => 'required', 'organization_id' => 'required',
'member_id' => 'required', 'member_id' => 'required',
'service_code' => 'required', 'service_code' => 'required',
'submission_date' => 'required' 'submission_date' => 'required',
'specialities_id' => 'required',
'dppj' => 'required',
], [ ], [
'organization_id.required' => trans('Validation.required',['attribute' => 'Provider ID']), 'organization_id.required' => trans('Validation.required',['attribute' => 'Provider ID']),
'member_id.required' => trans('Validation.required',['attribute' => 'Member ID']), 'member_id.required' => trans('Validation.required',['attribute' => 'Member ID']),
'service_code.required' => trans('Validation.required',['attribute' => 'Service Code']), 'service_code.required' => trans('Validation.required',['attribute' => 'Service Code']),
'submission_date.required' => trans('Validation.required',['attribute' => 'Submission Date']), 'submission_date.required' => trans('Validation.required',['attribute' => 'Submission Date']),
'specialities_id.required' => trans('Validation.required',['attribute' => 'Specialities']),
'dppj.required' => trans('Validation.required',['attribute' => 'DPJP']),
]); ]);
} }
if ($validator->fails()) if ($validator->fails())
@@ -245,7 +253,7 @@ class RequestLogController extends Controller
'request_logs.approved_at') 'request_logs.approved_at')
->paginate($limit); ->paginate($limit);
return response()->json(Helper::paginateResources($results)); return response()->json(Helper::paginateResources($results));
} }

View File

@@ -12,6 +12,8 @@ use App\Helpers\Helper;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory; use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder; use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Common\Entity\Style\CellAlignment; use Box\Spout\Common\Entity\Style\CellAlignment;
use Illuminate\Support\Facades\Storage;
use App\Services\ExportExcelService;
class InvoicePaymentController extends Controller class InvoicePaymentController extends Controller
{ {
@@ -30,15 +32,11 @@ class InvoicePaymentController extends Controller
$query->orderBy($orderBy, $direction); $query->orderBy($orderBy, $direction);
}) })
->when($request->input('start_date') , function ($query, $start_date) { ->when($request->input('start_date'), function ($query, $start_date) {
$query->where(function ($query) use ($start_date) { $query->whereDate('invoice_payments.created_at', '>=', $start_date);
$query->where('invoice_payments.created_at', '>=', $start_date);
});
}) })
->when($request->input('end_date') , function ($query, $end_date) { ->when($request->input('end_date'), function ($query, $end_date) {
$query->where(function ($query) use ($end_date) { $query->whereDate('invoice_payments.created_at', '<=', $end_date);
$query->where('invoice_payments.created_at', '<=', $end_date);
});
}) })
->when($request->input('status') , function ($query, $status) { ->when($request->input('status') , function ($query, $status) {
$query->where(function ($query) use ($status) { $query->where(function ($query) use ($status) {
@@ -183,8 +181,10 @@ class InvoicePaymentController extends Controller
->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id')
->leftJoin('members', 'request_logs.member_id', '=', 'members.id') ->leftJoin('members', 'request_logs.member_id', '=', 'members.id')
->where('invoice_payment_details.invoice_payment_id', $id) ->where('invoice_payment_details.invoice_payment_id', $id)
->whereNull('invoice_payment_details.deleted_by') ->where(function ($q) {
->orWhere('invoice_payment_details.deleted_by', 0) $q->whereNull('invoice_payment_details.deleted_by')
->orWhere('invoice_payment_details.deleted_by', 0);
})
->select( ->select(
'claim_requests.id', 'claim_requests.id',
'request_logs.invoice_no', 'request_logs.invoice_no',
@@ -240,6 +240,7 @@ class InvoicePaymentController extends Controller
'invoice_payments.id', 'invoice_payments.id',
'files.id as file_id', 'files.id as file_id',
'invoice_payments.amount_paid', 'invoice_payments.amount_paid',
'invoice_payments.no_reference',
'invoice_payments.payment_number', 'invoice_payments.payment_number',
'files.path', 'files.path',
'files.source', 'files.source',
@@ -274,6 +275,7 @@ class InvoicePaymentController extends Controller
return [ return [
'id' => $group->first()->id, 'id' => $group->first()->id,
'amount_paid' => $group->first()->amount_paid, 'amount_paid' => $group->first()->amount_paid,
'no_reference' => $group->first()->no_reference,
'payment_number' => $group->first()->payment_number, 'payment_number' => $group->first()->payment_number,
'files' => $group->map(function ($file) { 'files' => $group->map(function ($file) {
return [ return [
@@ -349,6 +351,7 @@ class InvoicePaymentController extends Controller
'start_date' => $request->start_date, 'start_date' => $request->start_date,
'end_date' => $request->start_date, 'end_date' => $request->start_date,
'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']),
'no_reference' => $valuePayments['noReference'],
'status' => 'submitted', 'status' => 'submitted',
'created_by' => auth()->user()->id, 'created_by' => auth()->user()->id,
'created_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s'),
@@ -405,40 +408,56 @@ class InvoicePaymentController extends Controller
'start_date' => $request->start_date, 'start_date' => $request->start_date,
'end_date' => $request->end_date, 'end_date' => $request->end_date,
'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']),
'no_reference' => $valuePayments['noReference'],
'updated_by' => auth()->user()->id, 'updated_by' => auth()->user()->id,
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
]); ]);
$existingClaims = DB::table('invoice_payment_details') $existingClaims = DB::table('invoice_payment_details')
->where('invoice_payment_id', $invoicePaymentId) ->where('invoice_payment_id', $invoicePaymentId)
->whereNull('deleted_at') // hanya data aktif
->pluck('claim_request_id') ->pluck('claim_request_id')
->map(fn($id) => (int)$id)
->toArray(); ->toArray();
$newClaims = $request->invoice_payment_details; // pastikan newClaims integer semua
$newClaims = array_map('intval', $request->invoice_payment_details);
// Data yang mau di-insert
// =============================
// INSERT DATA BARU
// =============================
$claimsToInsert = array_diff($newClaims, $existingClaims); $claimsToInsert = array_diff($newClaims, $existingClaims);
foreach ($claimsToInsert as $claim) {
DB::table('invoice_payment_details')->insert([ if (!empty($claimsToInsert)) {
'invoice_payment_id' => $invoicePaymentId, foreach ($claimsToInsert as $claim) {
'claim_request_id' => $claim, DB::table('invoice_payment_details')->insert([
'updated_by' => auth()->user()->id, 'invoice_payment_id' => $invoicePaymentId,
'updated_at' => now(), 'claim_request_id' => $claim,
]); 'created_by' => auth()->id(),
'created_at' => now(),
]);
}
} }
// Data yang mau di-delete (tidak ada di data baru)
// =============================
// SOFT DELETE DATA YANG DIHAPUS
// =============================
$claimsToDelete = array_diff($existingClaims, $newClaims); $claimsToDelete = array_diff($existingClaims, $newClaims);
if (!empty($claimsToDelete)) { if (!empty($claimsToDelete)) {
DB::table('invoice_payment_details') DB::table('invoice_payment_details')
->where('invoice_payment_id', $invoicePaymentId) ->where('invoice_payment_id', $invoicePaymentId)
->whereIn('claim_request_id', $claimsToDelete) ->whereIn('claim_request_id', $claimsToDelete)
->whereNull('deleted_at') // penting supaya tidak over-update
->update([ ->update([
'deleted_by' => auth()->user()->id, 'deleted_by' => auth()->id(),
'deleted_at' => now(), 'deleted_at' => now(),
]); ]);
} }
// Handle existing files // Handle existing files
$existingFiles = DB::table('files') $existingFiles = DB::table('files')
->where('files.fileable_id', $invoicePaymentId) ->where('files.fileable_id', $invoicePaymentId)
@@ -524,7 +543,22 @@ class InvoicePaymentController extends Controller
return response()->json(['message' => 'Status berhasil diperbarui']); return response()->json(['message' => 'Status berhasil diperbarui']);
} }
public function export(Request $request) public function export(Request $request, ExportExcelService $service)
{
\Log::info('EXPORT REQUEST', $request->all());
$filters = [
'search' => $request->input('search'),
'start_date' => $request->input('start_date'),
'end_date' => $request->input('end_date'),
'orderBy' => $request->input('orderBy'),
'order' => $request->input('order', 'asc'),
];
return $service->exportReport($filters, 'Report-Vale-Payment.xlsx');
}
public function export3(Request $request)
{ {
$start_date = $request->input('start_date') ? $request->input('start_date') : 'all'; $start_date = $request->input('start_date') ? $request->input('start_date') : 'all';
$end_date = $request->input('end_date') ? $request->input('end_date') : 'all'; $end_date = $request->input('end_date') ? $request->input('end_date') : 'all';

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Exports;
use App\Exports\Sheets\Invoices\VOPSheet;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use App\Exports\Sheets\UsersSheet;
use App\Exports\Sheets\OrdersSheet;
class ReportExport implements WithMultipleSheets
{
protected array $filters;
public function __construct(array $filters = [])
{
$this->filters = $filters;
}
public function sheets(): array
{
return [
new VOPSheet($this->filters),
];
}
}

View File

@@ -0,0 +1,349 @@
<?php
namespace App\Exports\Sheets\Invoices;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Cell\{
Cell,
DataType,
StringValueBinder
};
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Maatwebsite\Excel\Concerns\{
FromCollection,
WithHeadings,
WithMapping,
WithTitle,
WithCustomValueBinder,
WithEvents
};
use Maatwebsite\Excel\Events\AfterSheet;
class VOPSheet extends StringValueBinder implements
FromCollection,
WithHeadings,
WithMapping,
WithTitle,
WithCustomValueBinder,
WithEvents
{
protected array $filters;
protected int $no = 0;
protected float $grandTotal = 0;
protected array $benefits = [];
protected $benefitAmounts;
protected bool $benefitInitialized = false;
public function __construct(array $filters = [])
{
$this->filters = $filters;
}
public function collection()
{
$query = DB::table('invoice_payments')
->leftJoin('invoice_payment_details', 'invoice_payment_details.invoice_payment_id', '=', 'invoice_payments.id')
->leftJoin('claim_requests', 'claim_requests.id', '=', 'invoice_payment_details.claim_request_id')
->leftJoin('request_logs', 'request_logs.id', '=', 'claim_requests.request_log_id')
->leftJoin('organizations', 'organizations.id', '=', 'request_logs.organization_id')
->leftJoin('members', 'members.id', '=', 'request_logs.member_id')
->leftJoin('specialities', 'specialities.id', '=', 'request_logs.specialities_id')
->leftJoin('icd', DB::raw("icd.code"), '=', DB::raw("SUBSTRING_INDEX(request_logs.diagnosis, ',', 1)"))
->orderBy('request_logs.service_code', 'desc')
->select(
'invoice_payments.*',
'organizations.name as organization_name',
'members.name as member_name',
'request_logs.diagnosis as code_diagnosis',
'request_logs.service_code',
'request_logs.dppj',
'specialities.name as specialities_name',
'request_logs.submission_date as in',
'request_logs.discharge_date as out',
'request_logs.code as code_log',
'members.payor_id',
'request_logs.nominal as amount',
'claim_requests.request_log_id',
'members.member_id as batch_number',
'request_logs.keterangan',
'icd.name as diagnosis',
'request_logs.type_of_member'
);
if (!empty($this->filters['search'])) {
$query->where('invoice_number', 'like', '%' . $this->filters['search'] . '%');
}
if (!empty($this->filters['start_date'])) {
$query->whereDate('invoice_payments.created_at', '>=', $this->filters['start_date']);
}
if (!empty($this->filters['end_date'])) {
$query->whereDate('invoice_payments.created_at', '<=', $this->filters['end_date']);
}
if (!empty($this->filters['orderBy'])) {
$query->orderBy(
$this->filters['orderBy'],
$this->filters['order'] ?? 'asc'
);
}
$items = $query->get();
// ambil request_log_id
$requestLogIds = $items
->pluck('request_log_id')
->filter()
->unique()
->values();
// set benefit header (hanya yg dipakai)
$this->benefits = DB::table('request_log_benefits')
->join('benefits', 'benefits.id', '=', 'request_log_benefits.benefit_id')
->whereIn('request_log_benefits.request_log_id', $requestLogIds)
->select('benefits.id', 'benefits.description')
->distinct()
->orderBy('benefits.id')
->pluck('benefits.description', 'benefits.id')
->toArray();
// 🔥 preload benefit amount SEKALI (anti N+1)
$this->benefitAmounts = DB::table('request_log_benefits')
->whereIn('request_log_id', $requestLogIds)
->get()
->groupBy('request_log_id');
return $items;
}
public function map($item): array
{
$this->no++;
$this->grandTotal += (float) $item->amount_paid;
$benefitData = $this->getBenefitAmountsWithTotal($item->request_log_id);
return array_merge([
// $this->no,
(string) $item->invoice_number,
// (string) $item->payment_number,
'',
'',
'',
(string) $item->no_reference,
'',
'',
(string) ($item->code_log ?? '-'),
(string) ($item->payor_id ?? '-'),
'',
'',
(string) ($item->organization_name ?? '-'),
(string) ($item->member_name ?? '-'),
(string) ($item->batch_number ?? '-'),
(string) $benefitData['total'],
'',
'',
],
$benefitData['amounts'],
[
(string) ($item->in ?? '-'),
(string) ($item->out ?? '-'),
'',
(string) ($item->diagnosis ?? '-'),
(string) ($item->type_of_member ?? '-'), // omt
(string) ($item->service_code == 'OP' ? 'RJ' : 'RI'),
'',
'',
'',
(string) ($item->dppj ?? '-'),
(string) ($item->in ?? '-'),
(string) ($item->out ?? '-'),
(string) ($item->specialities_name ?? '-'),
'Karyawan',
'',
'',
'',
(string) ($item->keterangan ?? '-'),
// (string) $item->invoice_date,
// (string) $item->start_date,
// (string) $item->end_date,
// (float) $item->amount_paid,
// (string) $item->created_at,
// (string) $item->updated_at,
]);
}
public function headings(): array
{
$this->initBenefits();
return array_merge([
// 'No',
'No Invoice', //ok
// 'Pembayaran Ke',
'MONTH', //ok
'PERIODE SES', //ok
'SUBMIT INV/SES', //ok
'REFERENCE', //ok
'RECEIVED', //ok
'INV. DATE', //ok
'CODE', //ok
'VENDOR', //ok
'PO', //ok
'SES', //ok
'MAIN DESCRIPTION', //ok
'DESCRIPTION', //ok
'BN', //ok
'AMOUNT', //ok
'WBS', // ok
'NOTE', // ok
],
array_values($this->benefits),
[ 'DEPARTURE', // ok
'ARRIVED', // ok
'TEMPORARY DIAGNOSA', // ok
'FINAL DIAGNOSA',
'NOTE (OMT / NON OMT)',
'RI/RJ', // OK
'Dept', // OK
'Cost Center', // OK
'No.TA', // OK
'DPJP', // OK
'IN', // OK
'OUT', // OK
'SPECIALIST', // OK
'Employee', // OK
'Total Invoice sebelum BPJS', // OK
'BPJS', // OK
'Ditanggung Perusahaan', // OK
'KETERANGAN', // OK
// 'Tanggal Invoice',
// 'Start Date',
// 'End Date',
// 'Nominal Pembayaran',
// 'Created At',
// 'Updated At',
]);
}
public function title(): string
{
return 'VOP & VIP';
}
/**
* FOOTER: Grand Total
*/
public function registerEvents(): array
{
return [
AfterSheet::class => function (AfterSheet $event) {
// $event->sheet->getStyle('A1:AO1')
// ->getFont()
// ->setBold(true);
$highestColumn = $event->sheet->getDelegate()->getHighestColumn();
$event->sheet->getStyle('A1:' . $highestColumn . '1')
->getFont()
->setBold(true);
// $row = $event->sheet->getHighestRow() + 1;
// $event->sheet->setCellValue("A{$row}", 'Grand Total');
// $event->sheet->setCellValue("G{$row}", $this->grandTotal);
// // Bold footer
// $event->sheet->getStyle("A{$row}:G{$row}")
// ->getFont()
// ->setBold(true);
},
];
}
/**
* FORCE: object string
*/
public function bindValue(Cell $cell, $value)
{
if (is_object($value) || is_array($value)) {
$value = json_encode($value);
}
$cell->setValueExplicit($value, DataType::TYPE_STRING);
return true;
}
protected function getBenefitAmountsWithTotal(?int $requestLogId): array
{
if (!$requestLogId || !isset($this->benefitAmounts[$requestLogId])) {
return [
'amounts' => array_fill(0, count($this->benefits), 0),
'total' => 0,
];
}
$rows = $this->benefitAmounts[$requestLogId]
->pluck('amount_approved', 'benefit_id')
->toArray();
$amounts = [];
$total = 0;
foreach ($this->benefits as $benefitId => $desc) {
$amount = (float) ($rows[$benefitId] ?? 0);
$amounts[] = $amount;
$total += $amount;
}
// 🔥 kalau ini memang mau jadi grand total global
$this->grandTotal += $total;
return [
'amounts' => $amounts,
'total' => $total,
];
}
protected function initBenefits(): void
{
if ($this->benefitInitialized) {
return;
}
// ambil request_log_id dulu
$requestLogIds = DB::table('invoice_payments')
->leftJoin('invoice_payment_details', 'invoice_payment_details.invoice_payment_id', '=', 'invoice_payments.id')
->leftJoin('claim_requests', 'claim_requests.id', '=', 'invoice_payment_details.claim_request_id')
->whereNotNull('claim_requests.request_log_id')
->pluck('claim_requests.request_log_id')
->unique()
->values();
if ($requestLogIds->isEmpty()) {
$this->benefits = [];
return;
}
$this->benefits = DB::table('request_log_benefits')
->join('benefits', 'benefits.id', '=', 'request_log_benefits.benefit_id')
->whereIn('request_log_benefits.request_log_id', $requestLogIds)
->select('benefits.id', 'benefits.description')
->distinct()
->orderBy('benefits.id')
->pluck('benefits.description', 'benefits.id')
->toArray();
$this->benefitInitialized = true;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Services;
use App\Exports\ReportExport;
use Maatwebsite\Excel\Facades\Excel;
class ExportExcelService
{
public function exportReport(array $filters = [], string $filename = 'report.xlsx')
{
return Excel::download(new ReportExport($filters), $filename);
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoice_payments', function (Blueprint $table) {
$table->string('no_reference', 255)->after('amount_paid')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoice_payments', function (Blueprint $table) {
$table->dropColumn('no_reference');
});
}
};

View File

@@ -440,7 +440,7 @@ import { DatePicker, MobileDatePicker } from '@mui/x-date-pickers';
setIsLoading(false) setIsLoading(false)
enqueueSnackbar(response.data.message, { variant: 'success' }); enqueueSnackbar(response.data.message, { variant: 'success' });
setTimeout(() => { setTimeout(() => {
window.location.reload(); navigate('/invoice-payment');
}, 1000); }, 1000);
}) })
.catch(({ response }) => { .catch(({ response }) => {
@@ -911,7 +911,11 @@ const handleGetClaimDetails = async () => {
// Hitung Grand Total // Hitung Grand Total
const grandTotal = dataLogs.reduce((sum, item) => sum + item.totalBilling, 0); const grandTotal = dataLogs.reduce(
(sum, item) => sum + Number(item.totalBilling),
0
);
const [fileBukti, setFileBukti] = useState<any>([]); const [fileBukti, setFileBukti] = useState<any>([]);
const removeBuktiFiles = (filesState:any, index:any) => { const removeBuktiFiles = (filesState:any, index:any) => {
setFileBukti( setFileBukti(
@@ -976,7 +980,7 @@ const handleGetClaimDetails = async () => {
} }
}, [invoicePayment]); }, [invoicePayment]);
const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], paymentNumber: 1 }]); const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], noReference: "", paymentNumber: 1 }]);
const fileInputRefs = useRef({}); const fileInputRefs = useRef({});
useEffect(() => { useEffect(() => {
@@ -986,6 +990,7 @@ const handleGetClaimDetails = async () => {
amount: fCurrency(parseFloat(payment.amount_paid)), amount: fCurrency(parseFloat(payment.amount_paid)),
existingFiles: Array.isArray(payment.files) ? payment.files : [], existingFiles: Array.isArray(payment.files) ? payment.files : [],
files: [], files: [],
noReference: payment.no_reference,
paymentNumber: payment.payment_number paymentNumber: payment.payment_number
})); }));
setPayments(mappedPayments); setPayments(mappedPayments);
@@ -995,7 +1000,7 @@ const handleGetClaimDetails = async () => {
const handleAddPayment = () => { const handleAddPayment = () => {
const newId = Date.now(); const newId = Date.now();
setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], paymentNumber: payments.length + 1 }]); setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], noReference: "", paymentNumber: payments.length + 1 }]);
fileInputRefs.current[newId] = null; fileInputRefs.current[newId] = null;
}; };
@@ -1005,6 +1010,12 @@ const handleGetClaimDetails = async () => {
setPayments(updatedPayments); setPayments(updatedPayments);
}; };
const handleReferenceChange = (index, value) => {
const updatedPayments = [...payments];
updatedPayments[index].noReference = value;
setPayments(updatedPayments);
};
const handleFileChange = (index, event) => { const handleFileChange = (index, event) => {
const newFiles = Array.from(event.target.files); const newFiles = Array.from(event.target.files);
const updatedPayments = [...payments]; const updatedPayments = [...payments];
@@ -1029,9 +1040,14 @@ const handleGetClaimDetails = async () => {
}; };
const totalAmount = payments.reduce((sum, payment) => { const totalAmount = payments.reduce((sum, payment) => {
const num = parseFloat(payment.amount.replace(/[^\d]/g, "")) || 0; const num =
parseFloat(
(payment.amount ?? '0').toString().replace(/[^\d]/g, '')
) || 0;
return sum + num; return sum + num;
}, 0); }, 0);
return ( return (
@@ -1066,7 +1082,7 @@ const handleGetClaimDetails = async () => {
<Stack direction="row" spacing={2} width="100%"> <Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: '50%' }}> <Stack spacing={2} sx={{ width: '50%' }}>
<Typography variant="subtitle1">Nomor Invoice *</Typography> <Typography variant="subtitle1">Nomor Invoice <span style={{color: 'red'}}>*</span></Typography>
<TextField <TextField
id="nomor_invoice" id="nomor_invoice"
variant="outlined" variant="outlined"
@@ -1191,7 +1207,7 @@ const handleGetClaimDetails = async () => {
{/* Input Jumlah Bayar */} {/* Input Jumlah Bayar */}
<Stack direction="row" spacing={2} width="100%"> <Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: "50%" }}> <Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant="subtitle1">Nominal Pembayaran</Typography> <Typography variant="subtitle1">Nominal Pembayaran</Typography>
<TextField <TextField
variant="outlined" variant="outlined"
@@ -1202,7 +1218,7 @@ const handleGetClaimDetails = async () => {
</Stack> </Stack>
{/* Input File Bukti */} {/* Input File Bukti */}
<Stack spacing={2} sx={{ width: "50%" }}> <Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant="subtitle1">Upload Bukti Pembayaran</Typography> <Typography variant="subtitle1">Upload Bukti Pembayaran</Typography>
<Stack> <Stack>
<Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}> <Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}>
@@ -1262,6 +1278,15 @@ const handleGetClaimDetails = async () => {
/> />
</Stack> </Stack>
</Stack> </Stack>
<Stack spacing={2} sx={{ width: "30%" }}>
<Typography variant="subtitle1">No. Referensi</Typography>
<TextField
variant="outlined"
placeholder="Nomor Referensi"
value={payment.noReference}
onChange={(e) => handleReferenceChange(index, e.target.value)}
/>
</Stack>
</Stack> </Stack>
</Stack> </Stack>
))} ))}

View File

@@ -268,13 +268,13 @@ export default function Detail() {
{/* Input Jumlah Bayar */} {/* Input Jumlah Bayar */}
<Stack direction="row" spacing={2} width="100%"> <Stack direction="row" spacing={2} width="100%">
<Stack spacing={2} sx={{ width: "50%" }}> <Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Nominal Pembayaran</Typography> <Typography variant='subtitle2' sx={style1} gutterBottom>Nominal Pembayaran</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{fCurrency(parseFloat(file.amount_paid))}</Typography> <Typography variant='subtitle2' sx={style2} gutterBottom>{fCurrency(parseFloat(file.amount_paid))}</Typography>
</Stack> </Stack>
{/* Input File Bukti */} {/* Input File Bukti */}
<Stack spacing={2} sx={{ width: "50%" }}> <Stack spacing={2} sx={{ width: "35%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Bukti Pembayaran</Typography> <Typography variant='subtitle2' sx={style1} gutterBottom>Bukti Pembayaran</Typography>
<Stack> <Stack>
<Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}> <Stack divider={<Divider orientation="horizontal" flexItem />} spacing={1}>
@@ -292,6 +292,10 @@ export default function Detail() {
</Stack> </Stack>
</Stack> </Stack>
</Stack> </Stack>
<Stack spacing={2} sx={{ width: "30%" }}>
<Typography variant='subtitle2' sx={style1} gutterBottom>No. Reference</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{file.no_reference}</Typography>
</Stack>
</Stack> </Stack>
</Stack> </Stack>
))} ))}

View File

@@ -146,35 +146,60 @@ import { ClearOutlined, LoopOutlined } from "@mui/icons-material";
var filter = Object.fromEntries([...searchParams.entries()]); var filter = Object.fromEntries([...searchParams.entries()]);
setIsLoading(true) 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; const res = await axios.get('/invoice-payment/export', {
}) params: {
.catch((err) => // search: searchText,
enqueueSnackbar('Data Gagal di Export', { start_date: formattedDate ? formattedDate : null,
variant: 'error', end_date: formattedDate1,
anchorOrigin: { horizontal: 'right', vertical: 'top' }, provider: dataProvider,
}) status: dataStatus,
order: order,
orderBy: orderBy,
},
responseType: 'blob',
});
);
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'Report-Vale-Payment.xlsx');
document.body.appendChild(link);
link.click();
setIsLoading(false);
// 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' },
// })
// );
}; };

View File

@@ -140,7 +140,9 @@
"txtAttention": "Attention", "txtAttention": "Attention",
"txtAttentionInfo": "There are pending orders that require approval.", "txtAttentionInfo": "There are pending orders that require approval.",
"txtDPPJ": "DPJP", "txtDPPJ": "DPJP",
"txtSpecialist": "Specialist" "txtSpecialist": "Specialist",
"txtWarningDPPJ": "Please input DPJP",
"txtWarningSpecialist": "Please select Specialist"
} }

View File

@@ -140,5 +140,7 @@
"txtAttention": "Perhatian", "txtAttention": "Perhatian",
"txtAttentionInfo": "Terdapat pesanan pending mohon untuk segera di approve.", "txtAttentionInfo": "Terdapat pesanan pending mohon untuk segera di approve.",
"txtDPPJ": "DPJP", "txtDPPJ": "DPJP",
"txtSpecialist": "Spesialis" "txtSpecialist": "Spesialis",
"txtWarningDPPJ": "Mohon isi DPJP",
"txtWarningSpecialist": "Mohon pilih Spesialis"
} }

View File

@@ -95,6 +95,18 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
enqueueSnackbar(localeData.txtWarningDischargeDate, { variant: 'warning' }); enqueueSnackbar(localeData.txtWarningDischargeDate, { variant: 'warning' });
return false; return false;
} }
//cek spesialis
if(!idSpecialities)
{
enqueueSnackbar(localeData.txtWarningSpecialist, { variant: 'warning' });
return false;
}
//cek dpjp
if(!inputDppj)
{
enqueueSnackbar(localeData.txtWarningDPPJ, { variant: 'warning' });
return false;
}
setSubmitLoading(true); setSubmitLoading(true);
const formData = makeFormData({ const formData = makeFormData({
request_logs_id: member.id, request_logs_id: member.id,
@@ -142,7 +154,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
}).catch((error) => { }).catch((error) => {
console.error('Error fetching ICD options:', error); console.error('Error fetching ICD options:', error);
}); });
}, []); }, []);
console.log(serviceOptions, member, 'test') console.log(serviceOptions, member, 'test')
@@ -210,7 +222,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center', placeItems: 'center',
gap: 1, gap: 1,
placeContent: 'center', placeContent: 'center',
}} }}
> >
@@ -271,7 +283,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center', placeItems: 'center',
gap: 1, gap: 1,
placeContent: 'center', placeContent: 'center',
}} }}
> >
@@ -332,7 +344,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
placeItems: 'center', placeItems: 'center',
gap: 1, gap: 1,
placeContent: 'center', placeContent: 'center',
}} }}
> >
@@ -357,7 +369,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
<Stack direction="row" spacing={2} sx={{ width: '100%' }}> <Stack direction="row" spacing={2} sx={{ width: '100%' }}>
{/* Kolom Tanggal Discharge */} {/* Kolom Tanggal Discharge */}
<Stack spacing={2} sx={{ flex: 1 }}> <Stack spacing={2} sx={{ flex: 1 }}>
<Typography variant="subtitle1">{localeData.txtDischargeDate} *</Typography> <Typography variant="subtitle1">{localeData.txtDischargeDate} <span style={{ color: 'red' }}>*</span></Typography>
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker <DatePicker
label={localeData.txtDischargeDate} label={localeData.txtDischargeDate}
@@ -373,7 +385,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
{/* Kolom Service Type */} {/* Kolom Service Type */}
<Stack spacing={2} sx={{ flex: 1 }}> <Stack spacing={2} sx={{ flex: 1 }}>
<Typography variant="subtitle1">{localeData.txtDialogMember1} *</Typography> <Typography variant="subtitle1">{localeData.txtDialogMember1} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete <Autocomplete
id="service_type" id="service_type"
options={serviceOptions} options={serviceOptions}
@@ -393,7 +405,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
{/* Specialist */} {/* Specialist */}
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtSpecialist}</Typography> <Typography variant='subtitle1'>{localeData.txtSpecialist} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete <Autocomplete
id='specialities' id='specialities'
options={specialisOptions} options={specialisOptions}
@@ -412,7 +424,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{ localeData.txtDPPJ }</Typography> <Typography variant='subtitle1'>{ localeData.txtDPPJ } <span style={{ color: 'red' }}>*</span></Typography>
<TextField <TextField
id='dppj' id='dppj'
variant='outlined' variant='outlined'
@@ -426,7 +438,7 @@ export default function DialogFinalLog({ member, getData, onClose, handleSubmitS
</Stack> </Stack>
</Stack> </Stack>
<LoadingButton <LoadingButton
variant="contained" variant="contained"

View File

@@ -72,6 +72,18 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
enqueueSnackbar(localeData.txtDialogMember6, { variant: 'warning' }); enqueueSnackbar(localeData.txtDialogMember6, { variant: 'warning' });
return false; return false;
} }
//cek spesialis
if(!idSpecialities)
{
enqueueSnackbar(localeData.txtWarningSpecialist, { variant: 'warning' });
return false;
}
//cek dpjp
if(!inputDppj)
{
enqueueSnackbar(localeData.txtWarningDPPJ, { variant: 'warning' });
return false;
}
setSubmitLoading(true); setSubmitLoading(true);
const formData = { const formData = {
member_id: member.members.id, member_id: member.members.id,
@@ -145,7 +157,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtProvider} *</Typography> <Typography variant='subtitle1'>{localeData.txtProvider} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete <Autocomplete
id="provider" id="provider"
options={[{ name: localeData.txtAddNew, id: 0 }, ...member?.providers || []]} options={[{ name: localeData.txtAddNew, id: 0 }, ...member?.providers || []]}
@@ -172,7 +184,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
/> />
{showAddNewForm && ( {showAddNewForm && (
<Stack direction="column" spacing={1} padding={1}> <Stack direction="column" spacing={1} padding={1}>
<Typography variant='body2'>{localeData.txtAddNew} *</Typography> <Typography variant='body2'>{localeData.txtAddNew} <span style={{ color: 'red' }}>*</span></Typography>
<TextField <TextField
required={true} required={true}
label={localeData.txtName} label={localeData.txtName}
@@ -204,7 +216,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtDialogMember1} *</Typography> <Typography variant='subtitle1'>{localeData.txtDialogMember1} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete <Autocomplete
id="service_type" id="service_type"
options={member?.services || []} options={member?.services || []}
@@ -228,7 +240,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
{/* Specialist */} {/* Specialist */}
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtSpecialist}</Typography> <Typography variant='subtitle1'>{localeData.txtSpecialist} <span style={{ color: 'red' }}>*</span></Typography>
<Autocomplete <Autocomplete
id='specialities' id='specialities'
options={member?.specialities || []} options={member?.specialities || []}
@@ -251,7 +263,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{ localeData.txtDPPJ }</Typography> <Typography variant='subtitle1'>{ localeData.txtDPPJ } <span style={{ color: 'red' }}>*</span></Typography>
<TextField <TextField
id='dppj' id='dppj'
variant='outlined' variant='outlined'
@@ -266,7 +278,7 @@ export default function FormRequestClaim({ member, handleSubmitSuccess }: FormRe
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<Stack spacing={2} sx={{ width: '100%' }}> <Stack spacing={2} sx={{ width: '100%' }}>
<Typography variant='subtitle1'>{localeData.txtDialogMember5} *</Typography> <Typography variant='subtitle1'>{localeData.txtDialogMember5} <span style={{ color: 'red' }}>*</span></Typography>
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker <DatePicker
label={localeData.txtDialogMember5} label={localeData.txtDialogMember5}