Make cashier billing invoices actionable for walk-up payment.
Deploy Ladill Care / deploy (push) Successful in 1m1s
Deploy Ladill Care / deploy (push) Successful in 1m1s
Clarify booth Call next vs unpaid invoice list, and expose Pay/View row actions so cashiers can open bills when the booth queue is empty. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -89,7 +89,10 @@ class BillController extends Controller
|
|||||||
$result = $this->queueBridge->callNext($organization, CareQueueContexts::BILLING, $branchId, $member);
|
$result = $this->queueBridge->callNext($organization, CareQueueContexts::BILLING, $branchId, $member);
|
||||||
$ticket = $result['ticket'] ?? null;
|
$ticket = $result['ticket'] ?? null;
|
||||||
if (! $ticket) {
|
if (! $ticket) {
|
||||||
return back()->with('info', 'No bills waiting at your billing service point.');
|
return back()->with(
|
||||||
|
'info',
|
||||||
|
'No patients waiting at the billing booth. Open an unpaid invoice below to record a walk-up payment.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Called '.$ticket['ticket_number'].'.');
|
return back()->with('success', 'Called '.$ticket['ticket_number'].'.');
|
||||||
|
|||||||
@@ -29,17 +29,20 @@
|
|||||||
<button type="submit" class="btn-primary">Filter</button>
|
<button type="submit" class="btn-primary">Filter</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-4">
|
@if (! empty($canManageQueue) && ! empty($queueIntegration['enabled']))
|
||||||
@include('care.partials.queue-ops', [
|
<div class="mt-4">
|
||||||
'queueIntegration' => $queueIntegration ?? null,
|
@include('care.partials.queue-ops', [
|
||||||
'queueCallNextRoute' => 'care.bills.call-next',
|
'queueIntegration' => $queueIntegration,
|
||||||
'queueCallNextParams' => [],
|
'queueCallNextRoute' => 'care.bills.call-next',
|
||||||
'branchId' => $branchId ?? null,
|
'queueCallNextParams' => [],
|
||||||
'variant' => 'bar',
|
'queueCallNextDescription' => 'Call the next patient waiting at the billing booth. Unpaid invoices below stay open for walk-up payment.',
|
||||||
])
|
'branchId' => $branchId ?? null,
|
||||||
</div>
|
'variant' => 'bar',
|
||||||
|
])
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
<div class="mt-4 overflow-x-auto rounded-2xl border border-slate-200 bg-white">
|
||||||
<table class="min-w-full text-sm">
|
<table class="min-w-full text-sm">
|
||||||
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
|
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -49,12 +52,25 @@
|
|||||||
<th class="px-4 py-3">Total</th>
|
<th class="px-4 py-3">Total</th>
|
||||||
<th class="px-4 py-3">Balance</th>
|
<th class="px-4 py-3">Balance</th>
|
||||||
<th class="px-4 py-3">Status</th>
|
<th class="px-4 py-3">Status</th>
|
||||||
<th></th>
|
<th class="px-4 py-3 text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-50">
|
<tbody class="divide-y divide-slate-50">
|
||||||
@forelse ($bills as $bill)
|
@forelse ($bills as $bill)
|
||||||
<tr>
|
@php
|
||||||
|
$billUrl = route('care.bills.show', $bill);
|
||||||
|
$canServe = ! empty($queueIntegration['enabled'])
|
||||||
|
&& $bill->queue_ticket_uuid
|
||||||
|
&& ! in_array($bill->queue_ticket_status ?? '', ['serving', 'completed', 'cancelled'], true)
|
||||||
|
&& $bill->balance_minor > 0;
|
||||||
|
$actionLabel = $bill->balance_minor > 0 && $bill->status !== \App\Models\Bill::STATUS_VOID
|
||||||
|
? 'Pay'
|
||||||
|
: 'View';
|
||||||
|
@endphp
|
||||||
|
<tr
|
||||||
|
class="cursor-pointer hover:bg-slate-50"
|
||||||
|
onclick="window.location='{{ $billUrl }}'"
|
||||||
|
>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
@if (! empty($queueIntegration['enabled']) && $bill->queue_ticket_number)
|
@if (! empty($queueIntegration['enabled']) && $bill->queue_ticket_number)
|
||||||
@include('care.partials.queue-ticket', [
|
@include('care.partials.queue-ticket', [
|
||||||
@@ -65,20 +81,22 @@
|
|||||||
<span class="text-slate-400">—</span>
|
<span class="text-slate-400">—</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 font-mono text-xs">{{ $bill->invoice_number }}</td>
|
<td class="px-4 py-3 font-mono text-xs">
|
||||||
|
<a href="{{ $billUrl }}" class="font-medium text-sky-600 hover:text-sky-700" onclick="event.stopPropagation()">{{ $bill->invoice_number }}</a>
|
||||||
|
</td>
|
||||||
<td class="px-4 py-3">{{ $bill->patient->fullName() }}</td>
|
<td class="px-4 py-3">{{ $bill->patient->fullName() }}</td>
|
||||||
<td class="px-4 py-3">{{ $money($bill->total_minor) }}</td>
|
<td class="px-4 py-3">{{ $money($bill->total_minor) }}</td>
|
||||||
<td class="px-4 py-3 {{ $bill->balance_minor > 0 ? 'text-amber-700' : '' }}">{{ $money($bill->balance_minor) }}</td>
|
<td class="px-4 py-3 {{ $bill->balance_minor > 0 ? 'text-amber-700' : '' }}">{{ $money($bill->balance_minor) }}</td>
|
||||||
<td class="px-4 py-3">{{ $statuses[$bill->status] ?? $bill->status }}</td>
|
<td class="px-4 py-3">{{ $statuses[$bill->status] ?? $bill->status }}</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right" onclick="event.stopPropagation()">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<div class="flex items-center justify-end gap-2">
|
||||||
@if (! empty($queueIntegration['enabled']) && $bill->queue_ticket_uuid && ! in_array($bill->queue_ticket_status ?? '', ['serving', 'completed', 'cancelled'], true) && $bill->balance_minor > 0)
|
@if ($canServe)
|
||||||
<form method="POST" action="{{ route('care.bills.serve', $bill) }}">
|
<form method="POST" action="{{ route('care.bills.serve', $bill) }}">
|
||||||
@csrf
|
@csrf
|
||||||
<button type="submit" class="rounded-lg bg-sky-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-sky-700">Serve</button>
|
<button type="submit" class="rounded-lg bg-sky-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-sky-700">Serve</button>
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
<a href="{{ route('care.bills.show', $bill) }}" class="text-sky-600">View</a>
|
<a href="{{ $billUrl }}" class="rounded-lg border border-slate-200 bg-white px-2.5 py-1 text-xs font-medium text-slate-700 hover:border-slate-300 hover:bg-slate-50">{{ $actionLabel }}</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
$qi = $queueIntegration ?? null;
|
$qi = $queueIntegration ?? null;
|
||||||
$callNextRoute = $queueCallNextRoute ?? null;
|
$callNextRoute = $queueCallNextRoute ?? null;
|
||||||
$callNextParams = $queueCallNextParams ?? [];
|
$callNextParams = $queueCallNextParams ?? [];
|
||||||
|
$callNextDescription = $queueCallNextDescription ?? 'Bring the next waiting patient to your desk';
|
||||||
$currentAppointmentId = $currentAppointmentId ?? null;
|
$currentAppointmentId = $currentAppointmentId ?? null;
|
||||||
$variant = $variant ?? 'bar'; // bar | button
|
$variant = $variant ?? 'bar'; // bar | button
|
||||||
@endphp
|
@endphp
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<button type="submit" class="group flex w-full items-center justify-between gap-3 rounded-xl bg-indigo-600 px-4 py-3 text-left shadow-sm transition hover:bg-indigo-700">
|
<button type="submit" class="group flex w-full items-center justify-between gap-3 rounded-xl bg-indigo-600 px-4 py-3 text-left shadow-sm transition hover:bg-indigo-700">
|
||||||
<span class="min-w-0">
|
<span class="min-w-0">
|
||||||
<span class="block text-sm font-semibold text-white">Call next</span>
|
<span class="block text-sm font-semibold text-white">Call next</span>
|
||||||
<span class="mt-0.5 block truncate text-xs text-indigo-100">Bring the next waiting patient to your desk</span>
|
<span class="mt-0.5 block text-xs text-indigo-100">{{ $callNextDescription }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-white/15 text-white transition group-hover:bg-white/25">
|
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-white/15 text-white transition group-hover:bg-white/25">
|
||||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
||||||
|
|||||||
@@ -158,4 +158,39 @@ class CareBillTest extends TestCase
|
|||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee('INV-');
|
->assertSee('INV-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_cashier_bills_index_links_each_invoice_for_payment(): void
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->post(route('care.bills.generate', $this->visit));
|
||||||
|
|
||||||
|
$bill = Bill::firstOrFail();
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->get(route('care.bills.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee($bill->invoice_number)
|
||||||
|
->assertSee(route('care.bills.show', $bill), false)
|
||||||
|
->assertSee('Pay')
|
||||||
|
->assertSee('Actions');
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->get(route('care.bills.show', $bill))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Record payment');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_call_next_empty_booth_explains_walk_up_invoices(): void
|
||||||
|
{
|
||||||
|
\Illuminate\Support\Facades\Http::fake();
|
||||||
|
|
||||||
|
$settings = $this->organization->settings ?? [];
|
||||||
|
$settings['queue_integration_enabled'] = true;
|
||||||
|
$this->organization->update(['settings' => $settings]);
|
||||||
|
|
||||||
|
$this->actingAs($this->user)
|
||||||
|
->post(route('care.bills.call-next'), ['branch_id' => Branch::firstOrFail()->id])
|
||||||
|
->assertRedirect()
|
||||||
|
->assertSessionHas('info', 'No patients waiting at the billing booth. Open an unpaid invoice below to record a walk-up payment.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user