feat: laravel app instalasi sim-rs harapan kita
This commit is contained in:
parent
24b67f7810
commit
3dc3727505
@ -78,8 +78,8 @@ class TransactionController extends Controller
|
||||
{
|
||||
$patients = Patient::select(['id', 'name'])->get();
|
||||
$registrations = Registration::with(['patient:id,name'])
|
||||
->whereDoesntHave('transaction')
|
||||
->where('status', '!=', 'cancelled')
|
||||
->where('payment_status', '!=', 'paid')
|
||||
->select(['id', 'registration_number', 'patient_id'])
|
||||
->get()
|
||||
->map(function ($registration) {
|
||||
@ -90,7 +90,7 @@ class TransactionController extends Controller
|
||||
];
|
||||
});
|
||||
$insurances = Insurance::select(['id', 'name'])->get();
|
||||
$procedures = Procedure::select(['id', 'code', 'name'])->get();
|
||||
$procedures = Procedure::select(['id', 'code', 'name', 'base_price', 'tax_percentage', 'is_taxable'])->get();
|
||||
$employees = Employee::with('user:id,name')
|
||||
->whereHas('user')
|
||||
->get()
|
||||
@ -131,52 +131,49 @@ class TransactionController extends Controller
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
||||
// Validasi input dari request
|
||||
// Validate the main transaction data
|
||||
$validated = $request->validate([
|
||||
'registration_id' => 'required|exists:t_registration,id',
|
||||
'patient_id' => 'required|exists:m_patient,id',
|
||||
'insurance_id' => 'nullable|exists:m_insurance,id',
|
||||
'procedure_id' => 'required|exists:m_procedure,id',
|
||||
'employee_id' => 'nullable|exists:m_employee,id',
|
||||
'service_name' => 'required|string|max:100',
|
||||
'transaction_datetime' => 'required|date',
|
||||
'payment_datetime' => 'nullable|date',
|
||||
'due_date' => 'nullable|date',
|
||||
'procedure_id' => 'required|exists:m_procedure,id', // ID prosedur harus ada dan valid
|
||||
'quantity' => 'required|integer|min:1', // Jumlah harus ada, berupa integer dan minimal 1
|
||||
'subtotal' => 'required|numeric|min:0',
|
||||
'tax_amount' => 'required|numeric|min:0',
|
||||
'discount_amount' => 'nullable|numeric|min:0',
|
||||
'grand_total' => 'required|numeric|min:0',
|
||||
'paid_amount' => 'required|numeric|min:0',
|
||||
'insurance_covered_amount' => 'nullable|numeric|min:0',
|
||||
'payment_method' => 'required|in:cash,debit_card,credit_card,transfer,insurance,other',
|
||||
'payment_reference' => 'nullable|string|max:100',
|
||||
'status' => 'required|in:pending,paid,partially_paid,cancelled,refunded',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// Mulai transaksi database
|
||||
return DB::transaction(function () use ($validated) {
|
||||
// Generate nomor invoice
|
||||
// Start database transaction
|
||||
return DB::transaction(function () use ($validated, $request) {
|
||||
// Generate invoice number
|
||||
$invoiceNumber = Transaction::generateInvoiceNumber();
|
||||
|
||||
// Ambil harga prosedur
|
||||
// Get procedure details
|
||||
$procedure = Procedure::findOrFail($validated['procedure_id']);
|
||||
$unitPrice = $procedure->base_price;
|
||||
|
||||
// Hitung subtotal
|
||||
$subtotal = $unitPrice * $validated['quantity'];
|
||||
// Set default values for optional fields
|
||||
$discountAmount = $validated['discount_amount'] ?? 0;
|
||||
$insuranceCoveredAmount = $validated['insurance_covered_amount'] ?? 0;
|
||||
$patientResponsibility = $validated['grand_total'] - $insuranceCoveredAmount;
|
||||
|
||||
// Hitung pajak dan diskon jika ada
|
||||
$taxAmount = $subtotal * ($procedure->tax_percentage / 100);
|
||||
$discountAmount = 0; // Anda bisa menambahkan logika diskon jika diperlukan
|
||||
|
||||
// Hitung grand total
|
||||
$grandTotal = $subtotal + $taxAmount - $discountAmount;
|
||||
|
||||
// Hitung tanggung jawab pasien
|
||||
$patientResponsibility = $grandTotal - ($validated['insurance_id'] ? $validated['insurance_covered_amount'] : 0);
|
||||
|
||||
// Hitung jumlah kembalian jika pembayaran dilakukan
|
||||
// Calculate change amount if payment made
|
||||
$changeAmount = 0;
|
||||
if ($validated['paid_amount'] > $patientResponsibility) {
|
||||
$changeAmount = $validated['paid_amount'] - $patientResponsibility;
|
||||
}
|
||||
|
||||
// Buat transaksi
|
||||
// Create transaction
|
||||
$transaction = Transaction::create([
|
||||
'invoice_number' => $invoiceNumber,
|
||||
'registration_id' => $validated['registration_id'],
|
||||
@ -185,33 +182,37 @@ class TransactionController extends Controller
|
||||
'cashier_id' => auth()->id(),
|
||||
'transaction_datetime' => $validated['transaction_datetime'],
|
||||
'payment_datetime' => $validated['payment_datetime'],
|
||||
'due_date' => $validated['due_date'],
|
||||
'subtotal' => $subtotal,
|
||||
'tax_amount' => $taxAmount,
|
||||
'due_date' => $validated['due_date'] ?? null,
|
||||
'subtotal' => $validated['subtotal'],
|
||||
'tax_amount' => $validated['tax_amount'],
|
||||
'discount_amount' => $discountAmount,
|
||||
'grand_total' => $grandTotal,
|
||||
'grand_total' => $validated['grand_total'],
|
||||
'paid_amount' => $validated['paid_amount'],
|
||||
'change_amount' => $changeAmount,
|
||||
'insurance_covered_amount' => $validated['insurance_covered_amount'] ?? 0,
|
||||
'insurance_covered_amount' => $insuranceCoveredAmount,
|
||||
'patient_responsibility' => $patientResponsibility,
|
||||
'payment_method' => $validated['payment_method'],
|
||||
'payment_reference' => $validated['payment_reference'],
|
||||
'payment_reference' => $validated['payment_reference'] ?? null,
|
||||
'status' => $validated['status'],
|
||||
'notes' => $validated['notes'],
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
]);
|
||||
|
||||
// Buat detail transaksi
|
||||
// Create transaction detail
|
||||
TransactionDetail::create([
|
||||
'transaction_id' => $transaction->id,
|
||||
'procedure_id' => $validated['procedure_id'],
|
||||
'quantity' => $validated['quantity'],
|
||||
'unit_price' => $unitPrice,
|
||||
'performed_by' => $validated['employee_id'],
|
||||
'procedure_code' => $procedure->code,
|
||||
'procedure_name' => $validated['service_name'],
|
||||
'quantity' => 1, // Default quantity to 1
|
||||
'unit_price' => $validated['subtotal'],
|
||||
'discount_amount' => $discountAmount,
|
||||
'tax_amount' => $taxAmount,
|
||||
'subtotal' => $subtotal,
|
||||
'tax_amount' => $validated['tax_amount'],
|
||||
'subtotal' => $validated['subtotal'],
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
]);
|
||||
|
||||
// Update status pembayaran registrasi jika diperlukan
|
||||
// Update registration payment status if needed
|
||||
if ($validated['status'] === 'paid') {
|
||||
$registration = Registration::findOrFail($validated['registration_id']);
|
||||
$registration->update(['payment_status' => 'paid']);
|
||||
@ -220,7 +221,6 @@ class TransactionController extends Controller
|
||||
$registration->update(['payment_status' => 'partial']);
|
||||
}
|
||||
|
||||
// Redirect ke halaman transaksi dengan pesan sukses
|
||||
return redirect()->route('transactions.index')
|
||||
->with('status', 'Transaksi berhasil dibuat');
|
||||
});
|
||||
@ -274,6 +274,15 @@ class TransactionController extends Controller
|
||||
];
|
||||
});
|
||||
|
||||
// Get the first detail for the form
|
||||
$firstDetail = $transaction->details->first();
|
||||
|
||||
// Hitung ulang patient_responsibility jika diperlukan
|
||||
$patientResponsibility = $transaction->patient_responsibility;
|
||||
if ($transaction->payment_method === 'insurance') {
|
||||
$patientResponsibility = $transaction->grand_total - $transaction->insurance_covered_amount;
|
||||
}
|
||||
|
||||
return Inertia::render('transactions/form', [
|
||||
'mode' => 'edit',
|
||||
'transaction' => [
|
||||
@ -282,39 +291,25 @@ class TransactionController extends Controller
|
||||
'registration_id' => $transaction->registration_id,
|
||||
'patient_id' => $transaction->patient_id,
|
||||
'insurance_id' => $transaction->insurance_id,
|
||||
'cashier_id' => $transaction->cashier_id,
|
||||
'procedure_id' => $firstDetail ? $firstDetail->procedure_id : null,
|
||||
'employee_id' => $firstDetail ? $firstDetail->performed_by : null,
|
||||
'service_name' => $firstDetail ? $firstDetail->procedure_name : '',
|
||||
'transaction_datetime' => $transaction->transaction_datetime->format('Y-m-d\TH:i'),
|
||||
'payment_datetime' => $transaction->payment_datetime?->format('Y-m-d\TH:i'),
|
||||
'due_date' => $transaction->due_date?->format('Y-m-d'),
|
||||
'subtotal' => $transaction->subtotal,
|
||||
'tax_amount' => $transaction->tax_amount,
|
||||
'discount_amount' => $transaction->discount_amount,
|
||||
'subtotal' => (float) $transaction->subtotal,
|
||||
'tax_amount' => (float) $transaction->tax_amount,
|
||||
'discount_amount' => (float) $transaction->discount_amount,
|
||||
'discount_reason' => $transaction->discount_reason,
|
||||
'grand_total' => $transaction->grand_total,
|
||||
'paid_amount' => $transaction->paid_amount,
|
||||
'change_amount' => $transaction->change_amount,
|
||||
'insurance_covered_amount' => $transaction->insurance_covered_amount,
|
||||
'patient_responsibility' => $transaction->patient_responsibility,
|
||||
'grand_total' => (float) $transaction->grand_total,
|
||||
'paid_amount' => (float) $transaction->paid_amount,
|
||||
'change_amount' => (float) $transaction->change_amount,
|
||||
'insurance_covered_amount' => (float) $transaction->insurance_covered_amount,
|
||||
'patient_responsibility' => (float) $patientResponsibility,
|
||||
'payment_method' => $transaction->payment_method,
|
||||
'payment_reference' => $transaction->payment_reference,
|
||||
'status' => $transaction->status,
|
||||
'notes' => $transaction->notes,
|
||||
'details' => $transaction->details->map(function($detail) {
|
||||
return [
|
||||
'id' => $detail->id,
|
||||
'procedure_id' => $detail->procedure_id,
|
||||
'performed_by' => $detail->performed_by,
|
||||
'code' => $detail->code,
|
||||
'procedure_name' => $detail->procedure_name,
|
||||
'quantity' => $detail->quantity,
|
||||
'unit_price' => $detail->unit_price,
|
||||
'discount_amount' => $detail->discount_amount,
|
||||
'tax_amount' => $detail->tax_amount,
|
||||
'subtotal' => $detail->subtotal,
|
||||
'performed_datetime' => $detail->performed_datetime?->format('Y-m-d\TH:i'),
|
||||
'notes' => $detail->notes,
|
||||
];
|
||||
}),
|
||||
],
|
||||
'patients' => $patients,
|
||||
'registrations' => $registrations,
|
||||
@ -348,39 +343,40 @@ class TransactionController extends Controller
|
||||
'registration_id' => 'required|exists:t_registration,id',
|
||||
'patient_id' => 'required|exists:m_patient,id',
|
||||
'insurance_id' => 'nullable|exists:m_insurance,id',
|
||||
'procedure_id' => 'required|exists:m_procedure,id',
|
||||
'employee_id' => 'nullable|exists:m_employee,id',
|
||||
'service_name' => 'required|string|max:100',
|
||||
'transaction_datetime' => 'required|date',
|
||||
'payment_datetime' => 'nullable|date',
|
||||
'due_date' => 'nullable|date',
|
||||
'details' => 'required|array|min:1',
|
||||
'details.*.procedure_id' => 'required|exists:m_procedure,id',
|
||||
'details.*.quantity' => 'required|integer|min:1',
|
||||
'details.*.performed_by' => 'nullable|exists:m_employee,id',
|
||||
'details.*.notes' => 'nullable|string',
|
||||
'subtotal' => 'required|numeric|min:0',
|
||||
'tax_amount' => 'required|numeric|min:0',
|
||||
'discount_amount' => 'nullable|numeric|min:0',
|
||||
'grand_total' => 'required|numeric|min:0',
|
||||
'paid_amount' => 'required|numeric|min:0',
|
||||
'insurance_covered_amount' => 'nullable|numeric|min:0',
|
||||
'payment_method' => 'required|in:cash,debit_card,credit_card,transfer,insurance,other',
|
||||
'payment_reference' => 'nullable|string|max:100',
|
||||
'status' => 'required|in:pending,paid,partially_paid,cancelled,refunded',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
return DB::transaction(function () use ($validated, $transaction) {
|
||||
// Hitung total baru
|
||||
$subtotal = 0;
|
||||
$taxAmount = 0;
|
||||
$discountAmount = 0;
|
||||
// Get procedure details
|
||||
$procedure = Procedure::findOrFail($validated['procedure_id']);
|
||||
|
||||
foreach ($validated['details'] as $detail) {
|
||||
$procedure = Procedure::findOrFail($detail['procedure_id']);
|
||||
$unitPrice = $procedure->base_price;
|
||||
$detailSubtotal = $unitPrice * $detail['quantity'];
|
||||
$detailTax = $detailSubtotal * ($procedure->tax_percentage / 100);
|
||||
// Set default values for optional fields
|
||||
$discountAmount = $validated['discount_amount'] ?? 0;
|
||||
$insuranceCoveredAmount = $validated['insurance_covered_amount'] ?? 0;
|
||||
$patientResponsibility = $validated['grand_total'] - $insuranceCoveredAmount;
|
||||
|
||||
$subtotal += $detailSubtotal;
|
||||
$taxAmount += $detailTax;
|
||||
// Calculate change amount if payment made
|
||||
$changeAmount = 0;
|
||||
if ($validated['paid_amount'] > $patientResponsibility) {
|
||||
$changeAmount = $validated['paid_amount'] - $patientResponsibility;
|
||||
}
|
||||
|
||||
// Hitung grand total
|
||||
$grandTotal = $subtotal + $taxAmount - $discountAmount;
|
||||
|
||||
// Hitung tanggung jawab pasien
|
||||
$patientResponsibility = $grandTotal - ($validated['insurance_id'] ? $validated['insurance_covered_amount'] : 0);
|
||||
|
||||
// Update transaksi
|
||||
// Update transaction
|
||||
$transaction->update([
|
||||
'registration_id' => $validated['registration_id'],
|
||||
'patient_id' => $validated['patient_id'],
|
||||
@ -388,35 +384,50 @@ class TransactionController extends Controller
|
||||
'transaction_datetime' => $validated['transaction_datetime'],
|
||||
'payment_datetime' => $validated['payment_datetime'],
|
||||
'due_date' => $validated['due_date'],
|
||||
'subtotal' => $subtotal,
|
||||
'tax_amount' => $taxAmount,
|
||||
'subtotal' => $validated['subtotal'],
|
||||
'tax_amount' => $validated['tax_amount'],
|
||||
'discount_amount' => $discountAmount,
|
||||
'grand_total' => $grandTotal,
|
||||
'grand_total' => $validated['grand_total'],
|
||||
'paid_amount' => $validated['paid_amount'],
|
||||
'change_amount' => $changeAmount,
|
||||
'insurance_covered_amount' => $insuranceCoveredAmount,
|
||||
'patient_responsibility' => $patientResponsibility,
|
||||
'payment_method' => $validated['payment_method'],
|
||||
'payment_reference' => $validated['payment_reference'],
|
||||
'status' => $validated['status'],
|
||||
'notes' => $validated['notes'],
|
||||
]);
|
||||
|
||||
// Update detail transaksi
|
||||
foreach ($validated['details'] as $detail) {
|
||||
$detailData = [
|
||||
'transaction_id' => $transaction->id,
|
||||
'procedure_id' => $detail['procedure_id'],
|
||||
'performed_by' => $detail['performed_by'],
|
||||
'quantity' => $detail['quantity'],
|
||||
'unit_price' => Procedure::findOrFail($detail['procedure_id'])->base_price,
|
||||
'subtotal' => Procedure::findOrFail($detail['procedure_id'])->base_price * $detail['quantity'],
|
||||
'notes' => $detail['notes'] ?? null,
|
||||
];
|
||||
// Update the first transaction detail or create a new one
|
||||
$detail = TransactionDetail::where('transaction_id', $transaction->id)->first();
|
||||
$detailData = [
|
||||
'transaction_id' => $transaction->id,
|
||||
'procedure_id' => $validated['procedure_id'],
|
||||
'performed_by' => $validated['employee_id'],
|
||||
'procedure_code' => $procedure->code,
|
||||
'procedure_name' => $validated['service_name'],
|
||||
'quantity' => 1, // Default quantity to 1
|
||||
'unit_price' => $validated['subtotal'],
|
||||
'discount_amount' => $discountAmount,
|
||||
'tax_amount' => $validated['tax_amount'],
|
||||
'subtotal' => $validated['subtotal'],
|
||||
'notes' => $validated['notes'],
|
||||
];
|
||||
|
||||
if (isset($detail['id']) && $detail['id']) {
|
||||
TransactionDetail::where('id', $detail['id'])->update($detailData);
|
||||
} else {
|
||||
TransactionDetail::create($detailData);
|
||||
}
|
||||
if ($detail) {
|
||||
$detail->update($detailData);
|
||||
} else {
|
||||
TransactionDetail::create($detailData);
|
||||
}
|
||||
|
||||
// Update registration payment status if needed
|
||||
$registration = Registration::findOrFail($validated['registration_id']);
|
||||
if ($validated['status'] === 'paid') {
|
||||
$registration->update(['payment_status' => 'paid']);
|
||||
} elseif ($validated['status'] === 'partially_paid') {
|
||||
$registration->update(['payment_status' => 'partial']);
|
||||
}
|
||||
|
||||
// Redirect ke halaman transaksi dengan pesan sukses
|
||||
return redirect()->route('transactions.index')
|
||||
->with('status', 'Data transaksi berhasil diperbarui');
|
||||
});
|
||||
@ -429,7 +440,6 @@ class TransactionController extends Controller
|
||||
{
|
||||
return DB::transaction(function () use ($transaction) {
|
||||
TransactionDetail::where('transaction_id', $transaction->id)->delete();
|
||||
|
||||
$transaction->delete();
|
||||
|
||||
return redirect()->route('transactions.index')
|
||||
|
||||
@ -60,6 +60,11 @@ class Registration extends Model
|
||||
'is_active' => true,
|
||||
];
|
||||
|
||||
public function transaction()
|
||||
{
|
||||
return $this->hasOne(Transaction::class);
|
||||
}
|
||||
|
||||
public function patient()
|
||||
{
|
||||
return $this->belongsTo(Patient::class, 'patient_id');
|
||||
|
||||
@ -1,49 +1,57 @@
|
||||
import InputError from '@/components/input-error';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import AppLayout from '@/layouts/app-layout';
|
||||
import { Head } from '@inertiajs/react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { type BreadcrumbItem } from '@/types';
|
||||
import InputError from '@/components/input-error';
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
import { format } from 'date-fns';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface TransactionFormProps {
|
||||
mode: 'create' | 'edit';
|
||||
patients: Array<{ id: string; name: string }>;
|
||||
registrations: Array<{ id: string; registration_number: string; patient_name: string }>;
|
||||
insurances: Array<{ id: string; name: string }>;
|
||||
procedures: Array<{ id: string; code: string; name: string }>;
|
||||
procedures: Array<{
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
base_price?: number;
|
||||
tax_percentage?: number;
|
||||
is_taxable?: boolean;
|
||||
}>;
|
||||
employees: Array<{ id: string; name: string }>;
|
||||
paymentMethods: Record<string, string>;
|
||||
statusOptions: Record<string, string>;
|
||||
transaction?: {
|
||||
id?: string;
|
||||
transaction_number: string;
|
||||
registration_id?: string;
|
||||
patient_id?: string;
|
||||
invoice_number: string;
|
||||
registration_id: string;
|
||||
patient_id: string;
|
||||
insurance_id?: string;
|
||||
procedure_id?: string;
|
||||
employee_id?: string;
|
||||
service_name: string;
|
||||
description?: string;
|
||||
transaction_datetime: string;
|
||||
payment_datetime?: string;
|
||||
due_date?: string;
|
||||
subtotal: number;
|
||||
tax_amount: number;
|
||||
discount_amount: number;
|
||||
grand_total: number;
|
||||
payment_method: string;
|
||||
status: string;
|
||||
paid_amount: number;
|
||||
insurance_covered_amount: number;
|
||||
details?: string;
|
||||
patient_responsibility: number;
|
||||
payment_method: string;
|
||||
payment_reference?: string;
|
||||
status: string;
|
||||
notes?: string;
|
||||
};
|
||||
}
|
||||
@ -56,23 +64,22 @@ const breadcrumbs: BreadcrumbItem[] = [
|
||||
|
||||
const toLocalISOString = (date: Date) => {
|
||||
const offset = date.getTimezoneOffset();
|
||||
const localDate = new Date(date.getTime() - (offset * 60 * 1000));
|
||||
return localDate.toISOString().split('T')[0];
|
||||
const localDate = new Date(date.getTime() - offset * 60 * 1000);
|
||||
return localDate.toISOString().slice(0, 16);
|
||||
};
|
||||
|
||||
export default function TransactionForm({
|
||||
mode,
|
||||
patients,
|
||||
registrations,
|
||||
insurances,
|
||||
procedures,
|
||||
employees,
|
||||
paymentMethods,
|
||||
statusOptions,
|
||||
transaction
|
||||
}: TransactionFormProps) {
|
||||
mode,
|
||||
patients,
|
||||
registrations,
|
||||
insurances,
|
||||
procedures,
|
||||
employees,
|
||||
paymentMethods,
|
||||
statusOptions,
|
||||
transaction,
|
||||
}: TransactionFormProps) {
|
||||
const { data, setData, post, put, processing, errors, reset } = useForm({
|
||||
transaction_number: transaction?.transaction_number || '',
|
||||
registration_id: transaction?.registration_id || '',
|
||||
patient_id: transaction?.patient_id || '',
|
||||
insurance_id: transaction?.insurance_id || '',
|
||||
@ -80,40 +87,30 @@ export default function TransactionForm({
|
||||
employee_id: transaction?.employee_id || '',
|
||||
service_name: transaction?.service_name || '',
|
||||
transaction_datetime: transaction?.transaction_datetime || toLocalISOString(new Date()),
|
||||
payment_datetime: transaction?.payment_datetime || '',
|
||||
due_date: transaction?.due_date || '',
|
||||
subtotal: transaction?.subtotal || 0,
|
||||
tax_amount: transaction?.tax_amount || 0,
|
||||
discount_amount: transaction?.discount_amount || 0,
|
||||
grand_total: transaction?.grand_total || 0,
|
||||
payment_method: transaction?.payment_method || 'cash',
|
||||
status: transaction?.status || 'pending',
|
||||
paid_amount: transaction?.paid_amount || 0,
|
||||
insurance_covered_amount: transaction?.insurance_covered_amount || 0,
|
||||
patient_responsibility: transaction?.patient_responsibility || 0,
|
||||
payment_method: transaction?.payment_method || 'cash',
|
||||
payment_reference: transaction?.payment_reference || '',
|
||||
status: transaction?.status || 'pending',
|
||||
notes: transaction?.notes || '',
|
||||
details: transaction?.details || '',
|
||||
});
|
||||
|
||||
const [registrationSelected, setRegistrationSelected] = useState<boolean>(false);
|
||||
|
||||
// update pasien ketika memilih register
|
||||
useEffect(() => {
|
||||
const calculatedGrandTotal = data.subtotal + data.tax_amount - data.discount_amount;
|
||||
setData('grand_total', calculatedGrandTotal);
|
||||
|
||||
if (data.payment_method === 'insurance') {
|
||||
const insuranceCovered = calculatedGrandTotal * 0.8; // Example: insurance covers 80%
|
||||
setData('insurance_covered_amount', insuranceCovered);
|
||||
setData('paid_amount', calculatedGrandTotal - insuranceCovered);
|
||||
} else {
|
||||
setData('insurance_covered_amount', 0);
|
||||
setData('paid_amount', calculatedGrandTotal);
|
||||
}
|
||||
}, [data.subtotal, data.tax_amount, data.discount_amount, data.payment_method]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.registration_id) {
|
||||
const selectedRegistration = registrations.find(r => r.id === data.registration_id);
|
||||
if (data.registration_id && data.registration_id !== 'none') {
|
||||
const selectedRegistration = registrations.find((r) => r.id === data.registration_id);
|
||||
if (selectedRegistration) {
|
||||
setRegistrationSelected(true);
|
||||
const patient = patients.find(p => p.name === selectedRegistration.patient_name);
|
||||
const patient = patients.find((p) => p.name === selectedRegistration.patient_name);
|
||||
if (patient) {
|
||||
setData('patient_id', patient.id);
|
||||
}
|
||||
@ -123,15 +120,76 @@ export default function TransactionForm({
|
||||
}
|
||||
}, [data.registration_id, registrations, patients]);
|
||||
|
||||
// Update ruangan, subtotal dan pajak ketika prosedur di select
|
||||
useEffect(() => {
|
||||
if (data.procedure_id) {
|
||||
const selectedProcedure = procedures.find(p => p.id === data.procedure_id);
|
||||
if (data.procedure_id && data.procedure_id !== 'manual') {
|
||||
const selectedProcedure = procedures.find((p) => p.id === data.procedure_id);
|
||||
if (selectedProcedure) {
|
||||
setData('service_name', selectedProcedure.name);
|
||||
const basePrice = selectedProcedure.base_price || 0;
|
||||
const taxAmount =
|
||||
selectedProcedure.is_taxable && selectedProcedure.tax_percentage ? basePrice * (selectedProcedure.tax_percentage / 100) : 0;
|
||||
|
||||
setData({
|
||||
...data,
|
||||
service_name: selectedProcedure.name,
|
||||
subtotal: basePrice,
|
||||
tax_amount: taxAmount,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [data.procedure_id, procedures]);
|
||||
|
||||
useEffect(() => {
|
||||
// Pastikan semua nilai numerik valid (gunakan 0 jika NaN)
|
||||
const subtotal = Number(data.subtotal) || 0;
|
||||
const taxAmount = Number(data.tax_amount) || 0;
|
||||
const discountAmount = Number(data.discount_amount) || 0;
|
||||
|
||||
const calculatedGrandTotal = subtotal + taxAmount - discountAmount;
|
||||
|
||||
let insuranceCovered = 0;
|
||||
if (data.payment_method === 'insurance' && data.insurance_id) {
|
||||
// Pastikan insurance covered tidak melebihi grand total
|
||||
insuranceCovered = Math.min(calculatedGrandTotal, calculatedGrandTotal * 0.8);
|
||||
}
|
||||
|
||||
const patientResponsibility = calculatedGrandTotal - insuranceCovered;
|
||||
|
||||
setData({
|
||||
...data,
|
||||
subtotal,
|
||||
tax_amount: taxAmount,
|
||||
discount_amount: discountAmount,
|
||||
grand_total: calculatedGrandTotal,
|
||||
insurance_covered_amount: insuranceCovered,
|
||||
patient_responsibility: patientResponsibility,
|
||||
// Update paid_amount hanya jika belum diisi atau jika nilainya tidak valid
|
||||
paid_amount: Number(data.paid_amount) || patientResponsibility,
|
||||
});
|
||||
}, [data.subtotal, data.tax_amount, data.discount_amount, data.payment_method, data.insurance_id]);
|
||||
|
||||
// Calculate grand_total, insurance_covered_amount and patient_responsibility
|
||||
useEffect(() => {
|
||||
const calculatedGrandTotal = data.subtotal + data.tax_amount - data.discount_amount;
|
||||
|
||||
let insuranceCovered = 0;
|
||||
if (data.payment_method === 'insurance' && data.insurance_id) {
|
||||
// Example: insurance covers 80% of the total
|
||||
insuranceCovered = calculatedGrandTotal * 0.8;
|
||||
}
|
||||
|
||||
const patientResponsibility = calculatedGrandTotal - insuranceCovered;
|
||||
|
||||
setData({
|
||||
...data,
|
||||
grand_total: calculatedGrandTotal,
|
||||
insurance_covered_amount: insuranceCovered,
|
||||
patient_responsibility: patientResponsibility,
|
||||
// Default paid amount to what patient is responsible for
|
||||
paid_amount: data.paid_amount || patientResponsibility,
|
||||
});
|
||||
}, [data.subtotal, data.tax_amount, data.discount_amount, data.payment_method, data.insurance_id]);
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (mode === 'create') {
|
||||
@ -159,25 +217,17 @@ export default function TransactionForm({
|
||||
|
||||
<div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">
|
||||
{mode === 'create' ? 'Tambah Transaksi Baru' : 'Edit Data Transaksi'}
|
||||
</h1>
|
||||
{mode === 'edit' && (
|
||||
<span className="text-xl">
|
||||
No. Transaksi {data.transaction_number}
|
||||
</span>
|
||||
)}
|
||||
<h1 className="text-2xl font-bold">{mode === 'create' ? 'Tambah Transaksi Baru' : 'Edit Data Transaksi'}</h1>
|
||||
{mode === 'edit' && transaction?.invoice_number && <span className="text-xl">No. Invoice: {transaction.invoice_number}</span>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{/* Registration Section */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="registration_id">No. Registrasi</Label>
|
||||
<Select
|
||||
value={data.registration_id}
|
||||
onValueChange={(value) => setData('registration_id', value)}
|
||||
>
|
||||
<Select value={data.registration_id} onValueChange={(value) => setData('registration_id', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih nomor registrasi" />
|
||||
</SelectTrigger>
|
||||
@ -214,17 +264,15 @@ export default function TransactionForm({
|
||||
<InputError message={errors.patient_id} />
|
||||
</div>
|
||||
|
||||
{/* Procedure Section */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="procedure_id">Prosedur</Label>
|
||||
<Select
|
||||
value={data.procedure_id}
|
||||
onValueChange={(value) => setData('procedure_id', value)}
|
||||
>
|
||||
<Select value={data.procedure_id} onValueChange={(value) => setData('procedure_id', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih prosedur" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="manual">Manual Input</SelectItem>
|
||||
<SelectItem value="manual">Input Manual</SelectItem>
|
||||
{procedures.map((procedure) => (
|
||||
<SelectItem key={procedure.id} value={procedure.id}>
|
||||
{procedure.code} - {procedure.name}
|
||||
@ -248,10 +296,7 @@ export default function TransactionForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="employee_id">Pelaksana Layanan</Label>
|
||||
<Select
|
||||
value={data.employee_id}
|
||||
onValueChange={(value) => setData('employee_id', value)}
|
||||
>
|
||||
<Select value={data.employee_id} onValueChange={(value) => setData('employee_id', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih pelaksana" />
|
||||
</SelectTrigger>
|
||||
@ -271,15 +316,15 @@ export default function TransactionForm({
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
"w-full justify-start text-left font-normal",
|
||||
!data.transaction_datetime && "text-muted-foreground"
|
||||
'w-full justify-start text-left font-normal',
|
||||
!data.transaction_datetime && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{data.transaction_datetime ? (
|
||||
format(new Date(data.transaction_datetime), "dd/MM/yyyy HH:mm")
|
||||
format(new Date(data.transaction_datetime), 'dd/MM/yyyy HH:mm')
|
||||
) : (
|
||||
<span>Pilih tanggal/waktu</span>
|
||||
)}
|
||||
@ -292,6 +337,17 @@ export default function TransactionForm({
|
||||
onSelect={(date) => date && setData('transaction_datetime', toLocalISOString(date))}
|
||||
initialFocus
|
||||
/>
|
||||
<div className="border-t p-3">
|
||||
<Input
|
||||
type="time"
|
||||
value={data.transaction_datetime ? data.transaction_datetime.split('T')[1] || '' : ''}
|
||||
onChange={(e) => {
|
||||
const date =
|
||||
data.transaction_datetime?.split('T')[0] || toLocalISOString(new Date()).split('T')[0];
|
||||
setData('transaction_datetime', `${date}T${e.target.value}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<InputError message={errors.transaction_datetime} />
|
||||
@ -303,8 +359,11 @@ export default function TransactionForm({
|
||||
<Input
|
||||
id="subtotal"
|
||||
type="number"
|
||||
value={data.subtotal}
|
||||
onChange={(e) => setData('subtotal', Math.max(0, parseFloat(e.target.value) || 0))}
|
||||
value={isNaN(data.subtotal) ? '' : data.subtotal}
|
||||
onChange={(e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
setData('subtotal', isNaN(value) ? 0 : value);
|
||||
}}
|
||||
placeholder="Subtotal"
|
||||
/>
|
||||
<InputError message={errors.subtotal} />
|
||||
@ -323,7 +382,7 @@ export default function TransactionForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="discount_amount">Diskon *</Label>
|
||||
<Label htmlFor="discount_amount">Diskon</Label>
|
||||
<Input
|
||||
id="discount_amount"
|
||||
type="number"
|
||||
@ -336,28 +395,15 @@ export default function TransactionForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Grand Total</Label>
|
||||
<div className="p-2 font-semibold text-xl">
|
||||
{formatCurrency(data.grand_total)}
|
||||
<div className="flex h-9 items-center rounded border p-2 text-xl font-semibold">
|
||||
{formatCurrency(isNaN(data.grand_total) ? 0 : data.grand_total)}
|
||||
</div>
|
||||
<InputError message={errors.grand_total} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment_method">Metode Pembayaran *</Label>
|
||||
<Select
|
||||
value={data.payment_method}
|
||||
onValueChange ={(value) => {
|
||||
setData('payment_method', value);
|
||||
if (value === 'insurance' && data.insurance_id) {
|
||||
const insuranceCovered = data.grand_total * 0.8;
|
||||
setData('insurance_covered_amount', insuranceCovered);
|
||||
setData('paid_amount', data.grand_total - insuranceCovered);
|
||||
} else {
|
||||
setData('insurance_covered_amount', 0);
|
||||
setData('paid_amount', data.grand_total);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select value={data.payment_method} onValueChange={(value) => setData('payment_method', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih metode pembayaran" />
|
||||
</SelectTrigger>
|
||||
@ -374,10 +420,7 @@ export default function TransactionForm({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="status">Status Pembayaran *</Label>
|
||||
<Select
|
||||
value={data.status}
|
||||
onValueChange={(value) => setData('status', value)}
|
||||
>
|
||||
<Select value={data.status} onValueChange={(value) => setData('status', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih status pembayaran" />
|
||||
</SelectTrigger>
|
||||
@ -396,15 +439,7 @@ export default function TransactionForm({
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="insurance_id">Asuransi *</Label>
|
||||
<Select
|
||||
value={data.insurance_id}
|
||||
onValueChange={(value) => {
|
||||
setData('insurance_id', value);
|
||||
const insuranceCovered = data.grand_total * 0.8;
|
||||
setData('insurance_covered_amount', insuranceCovered);
|
||||
setData('paid_amount', data.grand_total - insuranceCovered);
|
||||
}}
|
||||
>
|
||||
<Select value={data.insurance_id} onValueChange={(value) => setData('insurance_id', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih asuransi" />
|
||||
</SelectTrigger>
|
||||
@ -427,8 +462,11 @@ export default function TransactionForm({
|
||||
value={data.insurance_covered_amount}
|
||||
onChange={(e) => {
|
||||
const insuranceCovered = Math.max(0, parseFloat(e.target.value) || 0);
|
||||
setData('insurance_covered_amount', insuranceCovered);
|
||||
setData('paid_amount', data.grand_total - insuranceCovered);
|
||||
setData({
|
||||
...data,
|
||||
insurance_covered_amount: insuranceCovered,
|
||||
patient_responsibility: data.grand_total - insuranceCovered,
|
||||
});
|
||||
}}
|
||||
placeholder="Jumlah dibayar asuransi"
|
||||
/>
|
||||
@ -437,6 +475,13 @@ export default function TransactionForm({
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="patient_responsibility">Tanggung Jawab Pasien</Label>
|
||||
<div className="flex h-9 items-center rounded border p-2 font-semibold">
|
||||
{formatCurrency(data.patient_responsibility)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="paid_amount">Jumlah Pembayaran *</Label>
|
||||
<Input
|
||||
@ -446,27 +491,65 @@ export default function TransactionForm({
|
||||
onChange={(e) => {
|
||||
const paidAmount = Math.max(0, parseFloat(e.target.value) || 0);
|
||||
setData('paid_amount', paidAmount);
|
||||
if (data.payment_method === 'insurance') {
|
||||
setData('insurance_covered_amount', data.grand_total - paidAmount);
|
||||
}
|
||||
}}
|
||||
placeholder="Jumlah pembayaran"
|
||||
/>
|
||||
<InputError message={errors.paid_amount} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 col-span-full">
|
||||
<Label htmlFor="details">Detail Layanan *</Label>
|
||||
<Textarea
|
||||
id="details"
|
||||
value={data.details}
|
||||
onChange={(e) => setData('details', e.target.value)}
|
||||
placeholder="Detail layanan yang diberikan"
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment_reference">Referensi Pembayaran</Label>
|
||||
<Input
|
||||
id="payment_reference"
|
||||
value={data.payment_reference}
|
||||
onChange={(e) => setData('payment_reference', e.target.value)}
|
||||
placeholder="No. Kartu/No. Referensi/dll"
|
||||
/>
|
||||
<InputError message={errors.details} />
|
||||
<InputError message={errors.payment_reference} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 col-span-full">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment_datetime">Tanggal/Waktu Pembayaran</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!data.payment_datetime && 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{data.payment_datetime ? (
|
||||
format(new Date(data.payment_datetime), 'dd/MM/yyyy HH:mm')
|
||||
) : (
|
||||
<span>Pilih tanggal/waktu pembayaran</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={data.payment_datetime ? new Date(data.payment_datetime) : undefined}
|
||||
onSelect={(date) => date && setData('payment_datetime', toLocalISOString(date))}
|
||||
initialFocus
|
||||
/>
|
||||
<div className="border-t p-3">
|
||||
<Input
|
||||
type="time"
|
||||
value={data.payment_datetime ? data.payment_datetime.split('T')[1] || '' : ''}
|
||||
onChange={(e) => {
|
||||
const date = data.payment_datetime?.split('T')[0] || toLocalISOString(new Date()).split('T')[0];
|
||||
setData('payment_datetime', `${date}T${e.target.value}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<InputError message={errors.payment_datetime} />
|
||||
</div>
|
||||
|
||||
<div className="col-span-full space-y-2">
|
||||
<Label htmlFor="notes">Catatan</Label>
|
||||
<Textarea
|
||||
id="notes"
|
||||
@ -479,7 +562,7 @@ export default function TransactionForm({
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" type="button" onClick ={() => window.history.back()}>
|
||||
<Button variant="outline" type="button" onClick={() => window.history.back()}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button type="submit" disabled={processing}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user